完整预订系统
高级
这是一个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)可能需要您自行付费。
相关工作流推荐
竞争对手内容差距分析器:自动化网站主题映射
使用Gemini AI、Apify和Google Sheets分析竞争对手内容差距
If
Set
Code
+10
30 节点Mychel Garzon
杂项
使用Groq AI和GhostGenius比较LinkedIn个人资料与职位描述
使用Groq AI和GhostGenius比较LinkedIn个人资料与职位描述的匹配度
If
Set
Code
+8
17 节点Stephan Koning
杂项
每日 WhatsApp 群组智能分析:GPT-4.1 分析与语音消息转录
每日 WhatsApp 群组智能分析:GPT-4.1 分析与语音消息转录
If
Set
Code
+20
52 节点Daniel Lianes
杂项
使用PageSpeed Insights监控网站性能并保存到Google Sheets并发送警报
使用PageSpeed Insights监控网站性能,发送警报到Google Sheets
If
Set
Code
+8
20 节点Dahiana
开发运维
微学习创建器(改进版)
使用 GPT-4 和 Google Docs 将长内容转换为碎片化学习模块
If
Set
Code
+9
25 节点inderjeet Bhambra
内容创作
教练入职与培训自动化
使用短信、Twilio和Google表格自动化30天教练培训
If
Set
Code
+7
36 节点Ronnie Craig
内容创作
工作流信息
难度等级
高级
节点数量41
分类2
节点类型8
作者
Sean Lon
@seanlonI 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 查看 →
分享此工作流