8
n8n 中文网amn8n.com

完整预订系统

高级

这是一个Miscellaneous, Multimodal AI领域的自动化工作流,包含 41 个节点。主要使用 If, Set, Code, Wait, Webhook 等节点。 使用 Google Calendar、营业时间和 REST API 的完整预订系统

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "TEMPLATE_WORKFLOW_ID",
  "meta": {
    "instanceId": "TEMPLATE_INSTANCE_ID",
    "templateCredsSetupCompleted": true
  },
  "name": "完整预订系统",
  "tags": [
    {
      "id": "TEMPLATE_TAG_ID",
      "name": "Booking System",
      "createdAt": "2025-09-15T05:27:59.706Z",
      "updatedAt": "2025-09-15T05:27:59.706Z"
    }
  ],
  "nodes": [
    {
      "id": "f634fd9d-8ba4-4c93-9db1-582b1e8ad991",
      "name": "预订 Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -2464,
        -240
      ],
      "webhookId": "booking-webhook",
      "parameters": {
        "path": "make-booking",
        "options": {
          "allowedOrigins": "*"
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1
    },
    {
      "id": "011a2f41-f0c3-4cd0-9969-7da1cbc3f427",
      "name": "验证输入",
      "type": "n8n-nodes-base.code",
      "position": [
        -1968,
        -240
      ],
      "parameters": {
        "jsCode": "// Parse and validate incoming booking data\nconst inputData = $input.first().json.body ||{};\n\n// Extract booking information\nconst bookingData = {\n  name: inputData.name || '',\n  email: inputData.email || '',\n  phone: inputData.phone || '',\n  date: inputData.date || '',\n  time: inputData.time || '',\n  source: inputData.source || 'Unknown',\n  timestamp: new Date().toISOString()\n};\n\n// Validate required fields\nconst requiredFields = ['name', 'email', 'phone', 'date', 'time'];\nconst missingFields = requiredFields.filter(field => !bookingData[field]);\n\nif (missingFields.length > 0) {\n  return {\n    success: false,\n    error: `Missing required fields: ${missingFields.join(', ')}`,\n    bookingData: null\n  };\n}\n\n// Validate email format\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nif (!emailRegex.test(bookingData.email)) {\n  return {\n    success: false,\n    error: 'Invalid email format',\n    bookingData: null\n  };\n}\n\n// Validate phone format (basic validation)\nconst phoneRegex = /^[+]?[0-9\\s\\-()]{10,}$/;\nif (!phoneRegex.test(bookingData.phone)) {\n  return {\n    success: false,\n    error: 'Invalid phone number format',\n    bookingData: null\n  };\n}\n\n// Validate date format (YYYY-MM-DD)\nconst dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\nif (!dateRegex.test(bookingData.date)) {\n  return {\n    success: false,\n    error: 'Invalid date format. Please use YYYY-MM-DD',\n    bookingData: null\n  };\n}\n\n// Validate time format (HH:MM)\nconst timeRegex = /^\\d{2}:\\d{2}$/;\nif (!timeRegex.test(bookingData.time)) {\n  return {\n    success: false,\n    error: 'Invalid time format. Please use HH:MM',\n    bookingData: null\n  };\n}\n\nreturn {\n  success: true,\n  error: null,\n  bookingData: bookingData\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c6138e82-ab61-40ca-b910-5d3fd220ef06",
      "name": "验证检查",
      "type": "n8n-nodes-base.if",
      "position": [
        -1808,
        -240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "validation-success",
              "operator": {
                "type": "boolean",
                "operation": "equal"
              },
              "leftValue": "={{ $json.success }}",
              "rightValue": true
            },
            {
              "id": "2500dc60-1b39-487b-8d12-b977f6d557a7",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.success }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "657c2588-a78d-4a56-84a9-d05402027348",
      "name": "营业时间检查",
      "type": "n8n-nodes-base.code",
      "position": [
        -1056,
        -240
      ],
      "parameters": {
        "jsCode": "// Business hours validation for Malaysia timezone\nconst bookingData = $input.first().json.bookingData;\n\n// Parse date and time components\nconst [year, month, day] = bookingData.date.split('-').map(Number);\nconst [hour, minute] = bookingData.time.split(':').map(Number);\n\n// Create date object in Malaysia timezone (UTC+8)\n// Note: JavaScript Date constructor treats the date as local time when no timezone is specified\n// We need to account for Malaysia being UTC+8\nconst malaysiaTime = new Date(year, month - 1, day, hour, minute, 0);\n\n// Get day of week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)\nconst dayOfWeek = malaysiaTime.getDay();\nconst timeInMinutes = hour * 60 + minute;\n\n// Business hours: Mon-Fri (1-5), 9am-9pm (540-1260 minutes)\n// Excluding: 12-2pm (720-840 minutes), 6-8pm (1080-1200 minutes)\nconst isWeekday = dayOfWeek >= 1 && dayOfWeek <= 5;\nconst isWithinBusinessHours = timeInMinutes >= 540 && timeInMinutes <= 1260;\nconst isNotLunchBreak = !(timeInMinutes >= 720 && timeInMinutes <= 840);\nconst isNotDinnerBreak = !(timeInMinutes >= 1080 && timeInMinutes <= 1200);\n\n// Check if time is valid\nconst isValidTime = isWeekday && isWithinBusinessHours && isNotLunchBreak && isNotDinnerBreak;\n\n// Format the requested datetime for Google Calendar (in Malaysia timezone)\n// Create proper ISO string with Malaysia timezone offset\nconst startDateTime = new Date(year, month - 1, day, hour, minute, 0).toISOString().replace('Z', '+08:00');\nconst endDateTime = new Date(year, month - 1, day, hour + 1, minute, 0).toISOString().replace('Z', '+08:00');\n\n// Create proper ISO string with Malaysia timezone offset\nconst startDateTimeCal = new Date(year, month - 1, day, hour, minute, 0).toISOString().replace('Z', '+08:00').replace('.000', '');\nconst endDateTimeCal = new Date(year, month - 1, day, hour + 1, minute, 0).toISOString().replace('Z', '+08:00').replace('.000', '');\n\nlet errorMessage = '';\nif (!isWeekday) {\n  errorMessage = 'Sorry, we only accept bookings on weekdays (Monday to Friday).';\n} else if (!isWithinBusinessHours) {\n  errorMessage = 'Sorry, we only accept bookings between 9:00 AM and 9:00 PM Malaysia time.';\n} else if (!isNotLunchBreak) {\n  errorMessage = 'Sorry, we are closed for lunch from 12:00 PM to 2:00 PM Malaysia time.';\n} else if (!isNotDinnerBreak) {\n  errorMessage = 'Sorry, we are closed for dinner from 6:00 PM to 8:00 PM Malaysia time.';\n}\n\n\nreturn {\n  isValidTime: isValidTime,\n  errorMessage: errorMessage,\n  bookingData: bookingData,\n  startDateTimeCal: startDateTimeCal,\n  endDateTimeCal: endDateTimeCal,\n  startDateTime: startDateTime,\n  endDateTime: endDateTime,\n\n  \n  malaysiaTime: malaysiaTime.toLocaleString('en-MY', { timeZone: 'Asia/Kuala_Lumpur' }),\n  dayOfWeek: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek],\n  debug: {\n    parsedDate: bookingData.date,\n    parsedTime: bookingData.time,\n    dayOfWeek: dayOfWeek,\n    timeInMinutes: timeInMinutes,\n    isWeekday: isWeekday,\n    isWithinBusinessHours: isWithinBusinessHours,\n    isNotLunchBreak: isNotLunchBreak,\n    isNotDinnerBreak: isNotDinnerBreak\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f1696ada-2220-409f-a6d6-20a0af802a46",
      "name": "时间验证检查",
      "type": "n8n-nodes-base.if",
      "position": [
        -880,
        -240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "time-valid",
              "operator": {
                "type": "boolean",
                "operation": "equal"
              },
              "leftValue": "={{ $json.isValidTime }}",
              "rightValue": true
            },
            {
              "id": "2ad109e2-afbf-4ac5-a850-c5e21ec7c8bf",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isValidTime }}",
              "rightValue": ""
            },
            {
              "id": "fcec51d1-e421-46b0-9787-343004cd04ae",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.errorMessage }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1a83780e-5c40-4eda-b9bf-07db8db03c5c",
      "name": "检查可用性",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        -272
      ],
      "parameters": {
        "jsCode": "function checkCalendar(calendarEvents, bookingData,startDateTime,endDateTime){\n\n \n\n// Filter out events that might conflict\nconst conflictingEvents = calendarEvents.filter(event => {\n  const eventStart = new Date(event.json.start?.dateTime || event.json.start?.date);\n  const eventEnd = new Date(event.json.end?.dateTime || event.json.end?.date);\n  \n  // Check for overlap\n  return (eventStart < new Date(endDateTime) && eventEnd > new Date(startDateTime));\n});\n\nconst isAvailable = conflictingEvents.length === 0;\n\nlet availabilityMessage = '';\nif (!isAvailable) {\n  const conflictingTimes = conflictingEvents.map(event => {\n    const start = new Date(event.json.start?.dateTime || event.json.start?.date);\n    return start.toLocaleString('en-MY', { timeZone: 'Asia/Kuala_Lumpur' });\n  }).join(', ');\n  \n  availabilityMessage = `Sorry, the requested time slot is not available. We have existing bookings at: ${conflictingTimes}. Please choose a different time.`;\n}\n\nreturn {\n  isAvailable: isAvailable,\n  availabilityMessage: availabilityMessage,\n  conflictingEvents: conflictingEvents,\n  bookingData: bookingData,\n  startDateTime: startDateTime,\n  endDateTime: endDateTime\n};\n\n  \n} \n\n\n// PUBLIC HOLIDAY\n// Check if there are any conflicting events \nlet calendarEvents = $input.all();\nlet bookingData =$('Time Validation Check').first().json.bookingData;\nlet startDateTime = $('Time Validation Check').first().json.startDateTime\nlet endDateTime =$('Time Validation Check').first().json.endDateTime\n \nconst result1 = checkCalendar(calendarEvents, bookingData,startDateTime,endDateTime)\nif(!result1.isAvailable   ){\n  return result1\n}\n\n \n// MAIN CALENDAR\n// Check if there are any conflicting events \n\nlet calendarEvents2 = $('Check Calendar Availability - Main').all(); \n\nconst result2 = checkCalendar(calendarEvents2, bookingData,startDateTime,endDateTime)\nif(!result2.isAvailable   ){\n  return result2\n}\n\n\n \nreturn {result2, result1:result1, result22:result2}\n "
      },
      "typeVersion": 2
    },
    {
      "id": "d306380e-991b-4ed1-9e49-120416e9a368",
      "name": "可用性检查",
      "type": "n8n-nodes-base.if",
      "position": [
        496,
        -272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "slot-available",
              "operator": {
                "type": "boolean",
                "operation": "equal"
              },
              "leftValue": "={{ $json.isAvailable }}",
              "rightValue": true
            },
            {
              "id": "a2aeaf3c-a4a9-46c7-957b-71a83b5b43d4",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.isAvailable +\"\"}}",
              "rightValue": "\"true\""
            },
            {
              "id": "71292758-80a6-4c61-b6ee-9dcb6df7fef7",
              "operator": {
                "type": "array",
                "operation": "lengthLt",
                "rightType": "number"
              },
              "leftValue": "={{ $json.conflictingEvents }}",
              "rightValue": 1
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b99740f9-2f54-4463-8f5d-3ce5dfa0f6f8",
      "name": "创建日历事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1136,
        -272
      ],
      "parameters": {
        "end": "={{ $('Business Hours Check').all()[0].json.endDateTimeCal }}",
        "start": "={{ $('Business Hours Check').all()[0].json.startDateTimeCal }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_BOOKING_CALENDAR_ID@group.calendar.google.com",
          "cachedResultName": "Main Booking Calendar"
        },
        "additionalFields": {
          "color": "2",
          "summary": "=Booking with {{ $('Booking Webhook').first().json.body.name }}",
          "attendees": [
            "={{ $('Booking Webhook').first().json.body.email }}"
          ],
          "visibility": "default",
          "description": "=Booking with {{ $('Booking Webhook').first().json.body.name }}",
          "conferenceDataUi": {
            "conferenceDataValues": {
              "conferenceSolution": "hangoutsMeet"
            }
          },
          "guestsCanInviteOthers": false
        }
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ce008e1d-3ea2-47f7-9ad6-444b85011fb1",
      "name": "准备成功响应",
      "type": "n8n-nodes-base.code",
      "position": [
        1344,
        -272
      ],
      "parameters": {
        "jsCode": "\nconst calendarEvent = $input.first().json;\n\nconst bookingData =$('Booking Webhook').first().json.body\n\nreturn {\n  success: true,\n  message: 'Booking confirmed successfully!',\n  bookingDetails: {\n    name: bookingData.name,\n    email: bookingData.email,\n    phone: bookingData.phone,\n    date: bookingData.date,\n    time: bookingData.time,\n    eventId: calendarEvent.id,\n    eventLink: calendarEvent.htmlLink,\n    calendarEvent: calendarEvent\n  },\n  confirmationMessage: `Hi ${bookingData.name}, your booking has been confirmed for ${bookingData.date} at ${bookingData.time} Malaysia time. You will receive a calendar invitation shortly.`\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "bd5d586c-101f-43a0-996d-9daf56e16006",
      "name": "成功响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1520,
        -272
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    },
    {
      "id": "4809f9d6-0545-4929-9b7e-2fed1baef77d",
      "name": "错误响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1408,
        96
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    },
    {
      "id": "8ed8f9f8-0e0e-4672-9b96-3493090d0cc7",
      "name": "时间错误响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -480,
        112
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    },
    {
      "id": "f275c219-5f3e-432c-a56a-1e120b1b72a1",
      "name": "可用性错误响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        832,
        80
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    },
    {
      "id": "5eafcbd8-36ae-447b-9801-b599c154ae93",
      "name": "准备验证错误",
      "type": "n8n-nodes-base.code",
      "position": [
        -1568,
        96
      ],
      "parameters": {
        "jsCode": "// Prepare error response for validation failures\nconst errorData = $input.first().json;\n\nreturn {\n  success: false,\n  error: errorData.error,\n  message: 'Booking request failed validation',\n  details: errorData\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "05e0736b-2e25-4414-94f9-e065c7a2f78a",
      "name": "准备时间错误",
      "type": "n8n-nodes-base.code",
      "position": [
        -624,
        112
      ],
      "parameters": {
        "jsCode": "// Prepare error response for business hours violations\nconst errorData = $input.first().json;\n\nreturn {\n  success: false,\n  error: errorData.errorMessage,\n  message: 'Booking request outside business hours',\n  details: {\n    requestedTime: errorData.malaysiaTime,\n    dayOfWeek: errorData.dayOfWeek,\n    businessHours: 'Monday to Friday, 9:00 AM - 9:00 PM Malaysia time',\n    excludedHours: '12:00 PM - 2:00 PM (lunch), 6:00 PM - 8:00 PM (dinner)'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a4b2ce24-64ed-4554-817a-be4e8e1f219a",
      "name": "准备可用性错误",
      "type": "n8n-nodes-base.code",
      "position": [
        656,
        80
      ],
      "parameters": {
        "jsCode": "// Prepare error response for availability issues\nconst errorData = $input.first().json;\n\nreturn {\n  success: false,\n  error: errorData.availabilityMessage,\n  message: 'Requested time slot is not available',\n  details: {\n    requestedTime: errorData.bookingData.date + ' ' + errorData.bookingData.time,\n    conflictingEvents: errorData.conflictingEvents.length,\n    suggestion: 'Please try a different time slot'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b2b026d5-aace-4e35-96e0-50ad518becc4",
      "name": "检查日历可用性 - 公共假日",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        112,
        -272
      ],
      "parameters": {
        "limit": 10,
        "options": {
          "orderBy": "startTime",
          "timeMax": "={{ $('Business Hours Check').all()[0].json.endDateTime}}",
          "timeMin": "={{ $('Business Hours Check').all()[0].json.startDateTime }}",
          "singleEvents": true
        },
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "en.malaysia#holiday@group.v.calendar.google.com",
          "cachedResultName": "Holidays in Malaysia"
        },
        "operation": "getAll"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "21087fe4-a8f3-45ce-9311-271638b3ed01",
      "name": "检查日历可用性 - 主日历",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -64,
        -272
      ],
      "parameters": {
        "limit": 10,
        "options": {
          "orderBy": "startTime",
          "timeMax": "={{ $('Business Hours Check').all()[0].json.endDateTime}}",
          "timeMin": "={{ $('Business Hours Check').all()[0].json.startDateTime }}",
          "singleEvents": true
        },
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_BOOKING_CALENDAR_ID@group.calendar.google.com",
          "cachedResultName": "Main Booking Calendar"
        },
        "operation": "getAll"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "2b3e3f48-053c-4aca-a620-262220445eed",
      "name": "检查日历可用性",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1344,
        1952
      ],
      "parameters": {
        "limit": 10,
        "options": {
          "orderBy": "startTime",
          "timeMax": "={{ $('Validate Date1').first().json.checkData.date + 'T23:59:59+08:00' }}",
          "timeMin": "={{ $('Validate Date1').first().json.checkData.date + 'T00:00:00+08:00' }}",
          "singleEvents": true
        },
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_BOOKING_CALENDAR_ID@group.calendar.google.com",
          "cachedResultName": "Main Booking Calendar"
        },
        "operation": "getAll"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "657ea779-54a4-4b14-97f8-6ad9338ba6a9",
      "name": "验证日期1",
      "type": "n8n-nodes-base.code",
      "position": [
        -1744,
        1952
      ],
      "parameters": {
        "jsCode": "// Parse and validate incoming date data\nconst inputData = $('Booking Timeslot webhook').first().json.body;\n\n// Extract date information\nconst checkData = {\n  date: inputData.date || '',\n  timestamp: new Date().toISOString()\n};\n\n// Validate required fields\nif (!checkData.date) {\n  return {\n    success: false,\n    error: 'Missing required field: date',\n    checkData: null\n  };\n}\n\n// Validate date format (YYYY-MM-DD)\nconst dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\nif (!dateRegex.test(checkData.date)) {\n  return {\n    success: false,\n    error: 'Invalid date format. Please use YYYY-MM-DD',\n    checkData: null\n  };\n}\n\n// Parse date components\nconst [year, month, day] = checkData.date.split('-').map(Number);\nconst checkDate = new Date(year, month - 1, day);\nconst dayOfWeek = checkDate.getDay();\n\n// Check if it's a weekend\nconst isWeekend = dayOfWeek === 0 || dayOfWeek === 6;\n\nreturn {\n  success: true,\n  error: null,\n  checkData: checkData,\n  parsedDate: {\n    year: year,\n    month: month,\n    day: day,\n    dayOfWeek: dayOfWeek,\n    dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek],\n    isWeekend: isWeekend\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6986af41-125a-44a8-863c-c0bf287f391a",
      "name": "预订时段 webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -2336,
        1952
      ],
      "webhookId": "booking-webhook",
      "parameters": {
        "path": "check-booking-date",
        "options": {
          "allowedOrigins": "*"
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1
    },
    {
      "id": "682404b7-8e65-458c-88ab-8f7af92c0654",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3392,
        -544
      ],
      "parameters": {
        "color": 6,
        "width": 208,
        "height": 80,
        "content": "## 进行预订"
      },
      "typeVersion": 1
    },
    {
      "id": "020d4645-f547-46a8-b79d-f3474f410172",
      "name": "等待",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2272,
        -240
      ],
      "webhookId": "YOUR_WEBHOOK_ID",
      "parameters": {
        "amount": 2.2
      },
      "typeVersion": 1.1
    },
    {
      "id": "521cabc0-39ac-44e9-9cdc-eb278d00914f",
      "name": "等待1",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2128,
        1952
      ],
      "webhookId": "YOUR_WEBHOOK_ID",
      "parameters": {
        "amount": 2.2
      },
      "typeVersion": 1.1
    },
    {
      "id": "7baeedf5-55e5-4f29-be40-b1e16bd4ebe8",
      "name": "配置时段",
      "type": "n8n-nodes-base.set",
      "position": [
        -1968,
        1952
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "3bfa3c4b-14c7-4c54-b186-bd4df7faafde",
              "name": "configuredAvailableTimeSlots",
              "type": "array",
              "value": "  [     { \"time\": '09:30', \"display\": '9:30 AM - 10:30 AM', \"available\": true },      { \"time\": '10:30', \"display\": '10:30 AM - 11:30 AM', \"available\": true },      { \"time\": '11:30', \"display\": '11:30 AM - 12:30 PM', \"available\": true },     { \"time\": '11:30', \"display\": '12:30 AM - 2:30 PM', \"available\": false },      { \"time\": '14:30', \"display\": '2:30 PM - 3:30 PM', \"available\": true },      { \"time\": '15:30', \"display\": '3:30 PM - 4:30 PM', \"available\": true },      { \"time\": '16:30', \"display\": '4:30 PM - 5:30 PM', \"available\": true },      { \"time\": '17:30', \"display\": '5:30 PM - 6:30 PM', \"available\": true },     { \"time\": '17:30', \"display\": '6:30 PM - 8:30 PM', \"available\": false },      { \"time\": '20:30', \"display\": '8:30 PM - 9:30 PM', \"available\": true }     ]  "
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "8008974b-2b69-4c9f-a9eb-cc4a3dfc16bb",
      "name": "便签 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1616,
        -48
      ],
      "parameters": {
        "color": 3,
        "width": 384,
        "height": 304,
        "content": "## 处理验证"
      },
      "typeVersion": 1
    },
    {
      "id": "863e46b6-20c9-4d46-ac72-a8d8a276ae84",
      "name": "便签 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        -32
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 288,
        "content": "## 处理验证"
      },
      "typeVersion": 1
    },
    {
      "id": "9d5bb1b8-797a-45ae-8099-09e2dc52b29e",
      "name": "便签 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -64
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 304,
        "content": "## 处理验证"
      },
      "typeVersion": 1
    },
    {
      "id": "a6c9d369-708c-48d1-bd3c-bbdb99dde83b",
      "name": "便签6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        -480
      ],
      "parameters": {
        "color": 6,
        "width": 1120,
        "height": 736,
        "content": "## 检查日期时间"
      },
      "typeVersion": 1
    },
    {
      "id": "f63f18ce-12f1-4ad8-bb41-3b9c1238f462",
      "name": "便签7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1104,
        -464
      ],
      "parameters": {
        "color": 6,
        "width": 864,
        "height": 736,
        "content": "## 检查日期时间"
      },
      "typeVersion": 1
    },
    {
      "id": "579efc35-1b5f-4f50-bae5-10a1a1d3544f",
      "name": "便签8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2032,
        -464
      ],
      "parameters": {
        "color": 6,
        "width": 816,
        "height": 736,
        "content": "## 检查接收的输入"
      },
      "typeVersion": 1
    },
    {
      "id": "43bce511-7190-4152-8392-5b196b52e9b6",
      "name": "### 替换 Airtable 连接",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        -480
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 720,
        "content": "## 创建日历条目"
      },
      "typeVersion": 1
    },
    {
      "id": "192d0193-0783-40e3-b763-be38026cff03",
      "name": "便签10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2496,
        -464
      ],
      "parameters": {
        "color": 6,
        "width": 400,
        "height": 736,
        "content": "## 进行预订的端点"
      },
      "typeVersion": 1
    },
    {
      "id": "53d5e3b2-8d1b-4427-bc55-c1925bd79cc3",
      "name": "便签12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3392,
        -448
      ],
      "parameters": {
        "color": 6,
        "width": 840,
        "height": 2020,
        "content": "## 创建预订:预订端点"
      },
      "typeVersion": 1
    },
    {
      "id": "3e214651-c3b0-4166-a49f-bc6eb15fefe2",
      "name": "便签13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3408,
        1808
      ],
      "parameters": {
        "color": 4,
        "width": 840,
        "height": 2244,
        "content": "## 获取预订时段:获取预订时段端点"
      },
      "typeVersion": 1
    },
    {
      "id": "3e51b455-3bf6-4f0c-a7f6-55085adbcdec",
      "name": "便签11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2416,
        1808
      ],
      "parameters": {
        "color": 4,
        "width": 1712,
        "height": 416,
        "content": "## 按特定日期获取预订时段的端点"
      },
      "typeVersion": 1
    },
    {
      "id": "023484db-6e34-4a00-b19c-419c8e486ee4",
      "name": "便签14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4192,
        -448
      ],
      "parameters": {
        "color": 3,
        "width": 660,
        "height": 3200,
        "content": "## 演示"
      },
      "typeVersion": 1
    },
    {
      "id": "d210e469-05a7-42f5-91f2-9c5e571094cd",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3392,
        1680
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 80,
        "content": "## 获取日期的预订时段"
      },
      "typeVersion": 1
    },
    {
      "id": "d0249a98-bd41-4129-b443-acb4fd1f7426",
      "name": "检查公共假日日历",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1552,
        1952
      ],
      "parameters": {
        "limit": 10,
        "options": {
          "orderBy": "startTime",
          "timeMax": "={{ $json.checkData.date + 'T23:59:59+08:00' }}",
          "timeMin": "={{ $json.checkData.date + 'T00:00:00+08:00' }}",
          "singleEvents": true
        },
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "en.malaysia#holiday@group.v.calendar.google.com",
          "cachedResultName": "Holidays in Malaysia"
        },
        "operation": "getAll"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "22b73d2f-d8ca-40c8-87df-9eecc3e40db4",
      "name": "处理假日检查日历",
      "type": "n8n-nodes-base.code",
      "position": [
        -1120,
        1952
      ],
      "parameters": {
        "jsCode": "// Check if the date is a public holiday and calendar conflicts\nconst holidayEvents = $('Check Public Holiday Calendar').all();\nconst calendarEvents = $('Check Calendar Availability').all();\nconst dateInfo = $('Validate Date1').first().json;\nconst configTimeSlots = $('ConfigTimeSlots').first().json.configuredAvailableTimeSlots\n\n// Check if there are any holiday events for this date\nconst isHoliday = holidayEvents && holidayEvents[0] && holidayEvents[0].json && holidayEvents[0].json.summary;\nlet holidayName = '';\nlet holidayMessage = '';\n\nif (isHoliday) {\n  // Get the first holiday event name\n  holidayName = holidayEvents[0].json.summary || 'Public Holiday';\n  holidayMessage = `This is a non-working day: ${holidayName}`;\n}\n\n// Check if it's a weekend\nconst isWeekend = dateInfo.parsedDate.isWeekend;\nlet weekendMessage = '';\n\nif (isWeekend) {\n  weekendMessage = `This is a weekend (${dateInfo.parsedDate.dayName})`;\n}\n\n// Determine if it's a working day\nconst isWorkingDay = !isHoliday && !isWeekend;\n\n// Generate available time slots for working days\nlet availableSlots = [];\nif (isWorkingDay) {\n  // Business hours: 9am-9pm, excluding 12-2pm and 6-8pm\n  const slots =  configTimeSlots ||[];\n  \n  // Check each slot against calendar events for conflicts\n  const selectedDate = dateInfo.checkData.date;\n  \n  slots.forEach(slot => {\n    // Create start and end times for this slot\n    const slotStartTime = `${selectedDate}T${slot.time}:00+08:00`;\n    const slotEndTime = `${selectedDate}T${String(parseInt(slot.time.split(':')[0]) + 1).padStart(2, '0')}:${slot.time.split(':')[1]}:00+08:00`;\n    \n    // Check if any calendar events conflict with this slot\n    const hasConflict = calendarEvents.some(event => {\n      const eventStart = new Date(event.json.start?.dateTime || event.json.start?.date);\n      const eventEnd = new Date(event.json.end?.dateTime || event.json.end?.date);\n      const slotStart = new Date(slotStartTime);\n      const slotEnd = new Date(slotEndTime);\n      \n      // Check for overlap\n      return (eventStart < slotEnd && eventEnd > slotStart);\n    });\n    \n    if (hasConflict) {\n      slot.available = false;\n      slot.status = 'booked';\n    }\n  });\n  \n  availableSlots = slots;\n}\n\nreturn {\n  success: true,\n  isWorkingDay: isWorkingDay,\n  isHoliday: isHoliday,\n  isWeekend: isWeekend,\n  holidayName: holidayName,\n  holidayMessage: holidayMessage,\n  weekendMessage: weekendMessage,\n  availableSlots: availableSlots,\n  dateInfo: dateInfo,\n  holidayEvents: holidayEvents,\n  calendarEvents: calendarEvents,\n  totalConflicts: calendarEvents.length\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "989997f0-1ca6-48fb-a078-b160df5f5812",
      "name": "假日响应日历",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -896,
        1952
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "timezone": "Asia/Singapore",
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "TEMPLATE_ERROR_WORKFLOW_ID",
    "executionOrder": "v1",
    "executionTimeout": 60,
    "timeSavedPerExecution": 4
  },
  "versionId": "TEMPLATE_VERSION_ID",
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait1": {
      "main": [
        [
          {
            "node": "ConfigTimeSlots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Date1": {
      "main": [
        [
          {
            "node": "Check Public Holiday Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input": {
      "main": [
        [
          {
            "node": "Validation Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Booking Webhook": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ConfigTimeSlots": {
      "main": [
        [
          {
            "node": "Validate Date1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validation Check": {
      "main": [
        [
          {
            "node": "Business Hours Check",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Availability Check": {
      "main": [
        [
          {
            "node": "Create Calendar Event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Availability Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Availability": {
      "main": [
        [
          {
            "node": "Availability Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Time Error": {
      "main": [
        [
          {
            "node": "Time Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Business Hours Check": {
      "main": [
        [
          {
            "node": "Time Validation Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Calendar Event": {
      "main": [
        [
          {
            "node": "Prepare Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Time Validation Check": {
      "main": [
        [
          {
            "node": "Check Calendar Availability - Main",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Time Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Booking Timeslot webhook": {
      "main": [
        [
          {
            "node": "Wait1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Success Response": {
      "main": [
        [
          {
            "node": "Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Validation Error": {
      "main": [
        [
          {
            "node": "Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Availability Error": {
      "main": [
        [
          {
            "node": "Availability Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Calendar Availability": {
      "main": [
        [
          {
            "node": "Process Holiday Check Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Public Holiday Calendar": {
      "main": [
        [
          {
            "node": "Check Calendar Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Holiday Check Calendar": {
      "main": [
        [
          {
            "node": "Holiday Response Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Calendar Availability - Main": {
      "main": [
        [
          {
            "node": "Check Calendar Availability - public holiday",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Calendar Availability - public holiday": {
      "main": [
        [
          {
            "node": "Check Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 杂项, 多模态 AI

需要付费吗?

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

工作流信息
难度等级
高级
节点数量41
分类2
节点类型8
难度说明

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

作者
Sean Lon

Sean Lon

@seanlon

I embarked on my coding journey at the age of 13, driven by a deep passion for AI, automation, and engineering. Over the years, I've taken on various roles including Developer, Engineer, Senior Engineer, Architect, Principal Engineer, Freelance Consultant, and Head of Engineering.

外部链接
在 n8n.io 查看

分享此工作流