8
n8n 中文网amn8n.com

全球节假日冲突检测与会议重新安排

高级

这是一个Personal Productivity领域的自动化工作流,包含 23 个节点。主要使用 If, Set, Code, Slack, HttpRequest 等节点。 使用Google Calendar和Slack检测节假日冲突并建议会议重新安排

前置要求
  • Slack Bot Token 或 Webhook URL
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "K67zdPMDUtledQFV",
  "meta": {
    "instanceId": "189dde98270e9ce0f006f0e9deb96aa5e627396fc6279cac8902c9b06936984d"
  },
  "name": "全球节假日冲突检测与会议重新安排",
  "tags": [],
  "nodes": [
    {
      "id": "b487208f-33b3-4527-8e18-bb3c2a8746d7",
      "name": "每日检查",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -752,
        224
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a8f9264a-5dba-4801-9c1e-e12a59237b8b",
      "name": "工作流配置",
      "type": "n8n-nodes-base.set",
      "position": [
        -528,
        224
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "currentYear",
              "type": "number",
              "value": "={{ new Date().getFullYear() }}"
            },
            {
              "id": "id-2",
              "name": "nextWeekStart",
              "type": "string",
              "value": "={{ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
            },
            {
              "id": "id-3",
              "name": "nextWeekEnd",
              "type": "string",
              "value": "={{ new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
            },
            {
              "id": "id-4",
              "name": "slackChannel",
              "type": "string",
              "value": "C09FB9QQQTX"
            },
            {
              "id": "id-5",
              "name": "calendarId",
              "type": "string",
              "value": "c_91f92ee12632d48cc78642add679d75aa8aecb09abea89eadbc97cb17d2de336@group.calendar.google.com"
            },
            {
              "id": "b5660f95-e978-4a1a-9c33-225dfa4e9552",
              "name": "countryCodes",
              "type": "array",
              "value": "[\"US\", \"GB\", \"DE\", \"IN\", \"CN\", \"KR\", \"HK\"]"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "6472f288-c7e2-49da-bd32-61b91d9528b3",
      "name": "合并并过滤下周节假日",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        224
      ],
      "parameters": {
        "jsCode": "// Get the next week date range from Workflow Configuration\nconst nextWeekStart = new Date($('Workflow Configuration').first().json.nextWeekStart);\nconst nextWeekEnd = new Date($('Workflow Configuration').first().json.nextWeekEnd);\n\n// Combine all holiday data from the loop\nconst allHolidays = [];\n\n// Process all input items (which are the results from each loop iteration)\nfor (const item of $input.all()) {\n  // The HTTP Request node in the loop outputs the holiday array in item.json\n  if (Array.isArray(item.json)) {\n    for (const holiday of item.json) {\n      allHolidays.push(holiday);\n    }\n  }\n}\n\n// Filter holidays that fall within next week range\nconst nextWeekHolidays = allHolidays.filter(holiday => {\n  const holidayDate = new Date(holiday.date);\n  return holidayDate >= nextWeekStart && holidayDate <= nextWeekEnd;\n});\n\n// Map to desired output format\nconst formattedHolidays = nextWeekHolidays.map(holiday => ({\n  date: holiday.date,\n  name: holiday.name || holiday.localName,\n  country: holiday.countryCode === 'US' ? 'United States' : \n           holiday.countryCode === 'GB' ? 'United Kingdom' : \n           holiday.countryCode === 'DE' ? 'Germany' : \n           holiday.countryCode === 'IN' ? 'India' : \n           holiday.countryCode === 'CN' ? 'China' : \n           holiday.countryCode === 'KR' ? 'South Korea' : \n           holiday.countryCode === 'HK' ? 'Hong Kong' : holiday.countryCode,\n  countryCode: holiday.countryCode\n}));\n\n// Return all filtered holidays as a single item\nreturn [{\n  json: {\n    holidays: formattedHolidays\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "524535d0-6d38-4da8-a5c9-e775f4ff4e50",
      "name": "获取下周日历事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -304,
        272
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ $('Workflow Configuration').first().json.nextWeekEnd }}T23:59:59Z",
        "timeMin": "={{ $('Workflow Configuration').first().json.nextWeekStart }}T00:00:00Z",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Workflow Configuration').first().json.calendarId }}"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "typeVersion": 1.3
    },
    {
      "id": "5ed5e1c2-726a-43bd-8f49-ed40f3eac9fa",
      "name": "检测节假日冲突",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        224
      ],
      "parameters": {
        "jsCode": "// Get the single item containing the list of all holidays from the first input\nconst holidayList = $input.all(0)[0].json.holidays || [];\n\n// Get all items containing calendar events from the second input\nconst calendarEventItems = $input.all(1);\n\n// Create a map of holiday dates for quick lookup\nconst holidayMap = new Map();\nholidayList.forEach(holiday => {\n  const date = holiday.date;\n  if (!holidayMap.has(date)) {\n    holidayMap.set(date, []);\n  }\n  holidayMap.get(date).push({\n    name: holiday.name,\n    country: holiday.countryCode || holiday.country\n  });\n});\n\n// Detect conflicts by iterating through each calendar event item\nconst conflicts = [];\ncalendarEventItems.forEach(eventItem => {\n  const event = eventItem.json;\n  const eventStart = event.start?.dateTime || event.start?.date;\n  if (!eventStart) return; // Skip if no start time\n  \n  const eventDate = eventStart.split('T')[0];\n  \n  // Check if this date has any holidays\n  if (holidayMap.has(eventDate)) {\n    const holidaysOnDate = holidayMap.get(eventDate);\n    const eventTime = eventStart.includes('T') ? eventStart.split('T')[1].substring(0, 5) : 'All day';\n    const attendees = event.attendees ? event.attendees.map(a => a.email) : [];\n    \n    conflicts.push({\n      eventName: event.summary || 'Untitled Event',\n      eventDate: eventDate,\n      eventTime: eventTime,\n      holidayName: holidaysOnDate.map(h => h.name).join(', '),\n      affectedCountries: [...new Set(holidaysOnDate.map(h => h.country))].join(', '),\n      attendees: attendees,\n      eventId: event.id\n    });\n  }\n});\n\n// Return all found conflicts in a SINGLE item to ensure subsequent nodes run only once\nreturn [{\n  json: {\n    conflicts: conflicts,\n    totalConflicts: conflicts.length,\n    checkDate: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "32c956cf-781f-4548-96a9-9d758a7f22a3",
      "name": "检查是否发现冲突",
      "type": "n8n-nodes-base.if",
      "position": [
        752,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "array",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $('Detect Holiday Conflicts').item.json.conflicts }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "fd84ad56-55c4-49c4-9e60-368977208c0c",
      "name": "生成重新安排建议",
      "type": "n8n-nodes-base.code",
      "position": [
        1072,
        224
      ],
      "parameters": {
        "jsCode": "const conflicts = $input.first().json.conflicts || [];\n\n// Get the holiday list from the 'Merge and Filter' node which ran before the conflict detection\nconst holidays = $('Merge and Filter Next Week Holidays').first().json.holidays.map(h => h.date);\n\nfunction isHoliday(dateStr) {\n  return holidays.includes(dateStr);\n}\n\nfunction isWeekend(date) {\n  const day = date.getDay();\n  return day === 0 || day === 6; // Sunday or Saturday\n}\n\nfunction findNextAvailableDate(startDate) {\n  let currentDate = new Date(startDate);\n  currentDate.setDate(currentDate.getDate() + 1);\n  \n  for (let i = 0; i < 30; i++) {\n    const dateStr = currentDate.toISOString().split('T')[0];\n    if (!isWeekend(currentDate) && !isHoliday(dateStr)) {\n      const originalTime = startDate.toTimeString().split(' ')[0].substring(0, 5);\n      return { date: dateStr, time: originalTime };\n    }\n    currentDate.setDate(currentDate.getDate() + 1);\n  }\n  \n  return {\n    date: currentDate.toISOString().split('T')[0],\n    time: startDate.toTimeString().split(' ')[0].substring(0, 5)\n  };\n}\n\nconst enhancedConflicts = conflicts.map(conflict => {\n  const eventDateTime = conflict.eventTime === 'All day' ? new Date(conflict.eventDate) : new Date(`${conflict.eventDate}T${conflict.eventTime}`);\n  const suggestion = findNextAvailableDate(eventDateTime);\n  \n  return {\n    ...conflict,\n    suggestedDate: suggestion.date,\n    suggestedTime: suggestion.time,\n    originalDate: conflict.eventDate,\n    originalTime: conflict.eventTime\n  };\n});\n\nreturn [{\n  json: {\n    conflicts: enhancedConflicts,\n    totalConflicts: enhancedConflicts.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4bb37c98-7400-4e4c-9519-4cafa5555067",
      "name": "格式化 Slack 摘要",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        224
      ],
      "parameters": {
        "jsCode": "// Get the conflicts data from the previous node\nconst conflictsData = $input.first().json;\nconst conflicts = conflictsData.conflicts || [];\n\n// Use a single backslash \\n for newlines\nlet slackMessage = ':warning: *Holiday Conflict Alert* :warning:\\n\\n';\n\nif (conflicts.length === 0) {\n  slackMessage += 'No conflicts detected for next week. All clear! :white_check_mark:';\n} else {\n  slackMessage += `Found *${conflicts.length}* meeting(s) scheduled during public holidays next week:\\n\\n`;\n  \n  conflicts.forEach((conflict, index) => {\n    slackMessage += `*${index + 1}. ${conflict.eventName}*\\n`;\n    slackMessage += `:calendar: *Date:* ${conflict.originalDate}\\n`;\n    slackMessage += `:clock3: *Time:* ${conflict.originalTime}\\n`;\n    slackMessage += `:earth_americas: *Affected Countries:* ${conflict.affectedCountries}\\n`;\n    slackMessage += `:pushpin: *Holiday:* ${conflict.holidayName}\\n`;\n    \n    // Add suggestion if it exists\n    if (conflict.suggestedDate) {\n      slackMessage += `:bulb: *Suggestion:* Reschedule to ${conflict.suggestedDate} at ${conflict.suggestedTime}\\n`;\n    }\n    \n    slackMessage += '\\n---\\n\\n';\n  });\n  \n  slackMessage += ':point_right: Please review and reschedule these meetings to accommodate team members in affected regions.';\n}\n\n// Return the formatted message\nreturn [{\n  json: {\n    slackMessage: slackMessage\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d01a7818-dc28-4490-ad5b-e5963cd22cbd",
      "name": "发布 Slack 摘要",
      "type": "n8n-nodes-base.slack",
      "position": [
        1680,
        224
      ],
      "webhookId": "5337846c-6b31-45a2-80ac-6960f6350ab1",
      "parameters": {
        "text": "={{ $json.slackMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Workflow Configuration').first().json.slackChannel }}"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "d04fe2ce-a6f3-49d5-a384-39a3a201fa37",
      "name": "遍历项目",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -304,
        80
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "74320878-4302-440c-99ce-5aefefcb1ca2",
      "name": "获取公共节假日",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -80,
        32
      ],
      "parameters": {
        "url": "=https://date.nager.at/api/v3/PublicHolidays/{{ $('Workflow Configuration').first().json.currentYear }}/{{ $json.countryCodes }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "392bf796-1880-4a89-ad6b-7838a7a19351",
      "name": "注意:每日检查",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        64
      ],
      "parameters": {
        "color": "white",
        "height": 144,
        "content": "**目的:** 在每个工作日的早晨触发一次工作流。"
      },
      "typeVersion": 1
    },
    {
      "id": "0541544f-2e8e-41a7-b899-76de44829cca",
      "name": "注意:工作流配置",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -208
      ],
      "parameters": {
        "color": "white",
        "width": 256,
        "height": 208,
        "content": "**目的:** 定义变量的中心位置。"
      },
      "typeVersion": 1
    },
    {
      "id": "02931af5-a167-422c-ae32-53412ed0f4f4",
      "name": "注意:合并并过滤下周节假日",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 合并 API 结果并仅过滤到下周。"
      },
      "typeVersion": 1
    },
    {
      "id": "79ac359f-12a3-43ba-bf75-b987f37f482e",
      "name": "注意:获取下周日历事件",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        480
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 从 Google Calendar 读取下周窗口内的所有事件。"
      },
      "typeVersion": 1
    },
    {
      "id": "165237d2-da5e-408f-bb46-77be9bf4c38c",
      "name": "注意:检测节假日冲突",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        384,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 比较事件日期与节假日日期以查找冲突。"
      },
      "typeVersion": 1
    },
    {
      "id": "3b55ac06-aa21-44de-a6e0-37051e02cfa1",
      "name": "注意:检查是否发现冲突",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 保护分支;仅当存在冲突时继续。"
      },
      "typeVersion": 1
    },
    {
      "id": "fc01dc46-773b-4881-b646-2bcf21c7bc1f",
      "name": "注意:生成重新安排建议",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 建议下一个不是节假日/周末的工作日。"
      },
      "typeVersion": 1
    },
    {
      "id": "cec6277f-583d-4ba9-8063-2e956395bff5",
      "name": "注意:格式化 Slack 摘要",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1296,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 创建可读的 Slack 消息。"
      },
      "typeVersion": 1
    },
    {
      "id": "6d766a0d-bea6-4bbb-a824-127b53b9dc45",
      "name": "注意:发布 Slack 摘要",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        48
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 将摘要发布到 Slack。"
      },
      "typeVersion": 1
    },
    {
      "id": "8461d7cc-e39d-4c86-9c6f-96a7aa55ba4f",
      "name": "注意:遍历项目",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        -160
      ],
      "parameters": {
        "color": "white",
        "content": "**目的:** 遍历每个国家代码。"
      },
      "typeVersion": 1
    },
    {
      "id": "f857f854-7ed9-43c1-af05-7e596d8369dc",
      "name": "注意:获取公共节假日",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        -192
      ],
      "parameters": {
        "color": "white",
        "width": 288,
        "height": 192,
        "content": "**目的:** 调用 Nager.Date API 获取公共节假日。"
      },
      "typeVersion": 1
    },
    {
      "id": "48d8def6-1e2e-4e03-b963-a44f9e2c0de1",
      "name": "模板概览",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1632,
        -448
      ],
      "parameters": {
        "color": "yellow",
        "width": 688,
        "height": 752,
        "content": "## 此模板的功能"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c01575e0-ea13-45a5-bb0a-c771b4107645",
  "connections": {
    "Daily Check": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge and Filter Next Week Holidays",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Public Holidays",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Slack Digest": {
      "main": [
        [
          {
            "node": "Post Slack Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Public Holidays": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Next Week Calendar Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Conflicts Found": {
      "main": [
        [
          {
            "node": "Generate Reschedule Suggestions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Holiday Conflicts": {
      "main": [
        [
          {
            "node": "Check If Conflicts Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Reschedule Suggestions": {
      "main": [
        [
          {
            "node": "Format Slack Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge and Filter Next Week Holidays": {
      "main": [
        [
          {
            "node": "Detect Holiday Conflicts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 个人效率

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量23
分类1
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流