备份n8n工作流到OneDrive
高级
这是一个DevOps领域的自动化工作流,包含 29 个节点。主要使用 N8n, Code, Merge, Switch, ConvertToFile 等节点。 自动备份n8n工作流到OneDrive,包含清理和邮件通知功能
前置要求
- •无特殊前置要求,导入即可使用
使用的节点 (29)
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "k0dRcFrfMvlaqhT0",
"meta": {
"instanceId": "89249a8a187ba6e01e16112a0d334a3aa01d510ad8f88d223e12cc0a2a8beb6b"
},
"name": "备份 n8n 工作流到 Onedrive",
"tags": [],
"nodes": [
{
"id": "96aee703-bbf4-4de0-b6fb-7cf8428d5027",
"name": "检索工作流",
"type": "n8n-nodes-base.n8n",
"position": [
-4592,
96
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"typeVersion": 1
},
{
"id": "84b64870-e3c4-4762-9931-c5ec929c904e",
"name": "获取备份文件夹",
"type": "n8n-nodes-base.microsoftOneDrive",
"position": [
-3248,
96
],
"parameters": {
"resource": "folder"
},
"typeVersion": 1
},
{
"id": "cf46cd5f-f000-40ec-a6c8-110984437c6c",
"name": "删除旧备份文件夹",
"type": "n8n-nodes-base.microsoftOneDrive",
"position": [
-1344,
224
],
"parameters": {
"folderId": "={{ $json.id }}\n\n",
"resource": "folder",
"operation": "delete"
},
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "c79f2638-dd31-4c07-ad61-b25efc2919bd",
"name": "准备 HTML 邮件",
"type": "n8n-nodes-base.code",
"position": [
-480,
112
],
"parameters": {
"jsCode": "// Prepare backup success email data - SAFE for any flow path\nconst backupDate = DateTime.now();\n\n// SAFE: Get folder name - always available\nlet folderName = 'Unknown';\ntry {\n folderName = $('Create new backup folder').item.json.name;\n} catch (e) {\n console.log('Could not get folder name:', e.message);\n folderName = `n8n.backup Daily ${backupDate.toFormat('yyyy-MM-dd')}`;\n}\n\n// SAFE: Get original workflow data - always available\nlet originalWorkflowData = [];\nlet totalWorkflows = 0;\ntry {\n originalWorkflowData = $('Retrieve workflows').all();\n totalWorkflows = originalWorkflowData.length;\n} catch (e) {\n console.log('Could not get workflow data:', e.message);\n}\n\n// SAFE: Get uploaded files data - always available from input\nlet uploadedFiles = [];\ntry {\n uploadedFiles = $('Upload JSON to folder').all();\n} catch (e) {\n console.log('Could not get uploaded files:', e.message);\n // Fallback: try to get from current input if this is from Upload path\n try {\n uploadedFiles = $input.all();\n } catch (e2) {\n console.log('Could not get input data either:', e2.message);\n }\n}\n\n// Calculate statistics\nconst activeWorkflows = originalWorkflowData.filter(item => {\n return item.json && item.json.active === true;\n}).length;\nconst inactiveWorkflows = totalWorkflows - activeWorkflows;\n\n// Calculate total size from uploaded files\nconst totalSizeKB = uploadedFiles.reduce((sum, item) => {\n return sum + ((item.json && item.json.size) ? item.json.size : 0);\n}, 0) / 1024;\n\n// Enhanced workflow analysis\nconst workflowsByStatus = {\n active: [],\n inactive: [],\n recentlyModified: [],\n large: []\n};\n\n// Prepare workflow list\nconst workflowList = originalWorkflowData.map(item => {\n let lastUpdated = 'Unknown';\n let lastUpdatedDate = null;\n let status = 'Inactive';\n \n // Handle date from original workflow data\n try {\n if (item.json.updatedAt) {\n const date = DateTime.fromISO(item.json.updatedAt);\n if (date.isValid) {\n lastUpdated = date.toFormat('dd/MM/yyyy HH:mm');\n lastUpdatedDate = date;\n }\n } else if (item.json.lastModifiedDateTime) {\n const date = DateTime.fromISO(item.json.lastModifiedDateTime);\n if (date.isValid) {\n lastUpdated = date.toFormat('dd/MM/yyyy HH:mm');\n lastUpdatedDate = date;\n }\n } else if (item.json.createdAt) {\n const date = DateTime.fromISO(item.json.createdAt);\n if (date.isValid) {\n lastUpdated = date.toFormat('dd/MM/yyyy HH:mm');\n lastUpdatedDate = date;\n }\n }\n } catch (error) {\n console.log('Date parsing error for workflow:', item.json.name, error);\n lastUpdated = 'Date unavailable';\n }\n \n // Use active status from original data\n if (item.json && item.json.active === true) {\n status = 'Active';\n }\n \n // Find corresponding uploaded file to get size\n const uploadedFile = uploadedFiles.find(uf => uf.json && uf.json.name === item.json.name);\n const fileSize = uploadedFile ? Math.round((uploadedFile.json.size || 0) / 1024 * 100) / 100 : 0;\n \n const workflow = {\n name: (item.json && item.json.name) ? item.json.name : 'Unnamed workflow',\n active: status === 'Active',\n updatedAt: lastUpdated,\n lastUpdatedDate: lastUpdatedDate,\n size: fileSize,\n id: (item.json && item.json.id) ? item.json.id : 'unknown'\n };\n \n // Categorize workflows for insights\n if (workflow.active) {\n workflowsByStatus.active.push(workflow);\n } else {\n workflowsByStatus.inactive.push(workflow);\n }\n \n // Check if recently modified (last 7 days)\n if (lastUpdatedDate && lastUpdatedDate > DateTime.now().minus({ days: 7 })) {\n workflowsByStatus.recentlyModified.push(workflow);\n }\n \n // Check if large workflow (>50KB)\n if (fileSize > 50) {\n workflowsByStatus.large.push(workflow);\n }\n \n return workflow;\n}).sort((a, b) => {\n // Sort by date - newest first (nulls/unknowns go to end)\n if (!a.lastUpdatedDate && !b.lastUpdatedDate) return 0;\n if (!a.lastUpdatedDate) return 1;\n if (!b.lastUpdatedDate) return -1;\n return b.lastUpdatedDate - a.lastUpdatedDate;\n});\n\n// Calculate additional insights\nconst recentlyModifiedCount = workflowsByStatus.recentlyModified.length;\nconst largeWorkflowsCount = workflowsByStatus.large.length;\nconst averageWorkflowSize = totalWorkflows > 0 ? Math.round((totalSizeKB / totalWorkflows) * 100) / 100 : 0;\n\n// FIXED: SAFE cleanup status detection - handles all possible flow paths\nlet cleanupStatus = 'Not Attempted';\nlet cleanupNote = 'Cleanup status could not be determined';\nlet cleanupIcon = '❓';\nlet cleanupColor = '#6b7280';\n\n// Try to determine cleanup status by checking which nodes were executed\ntry {\n // Method 1: Check if we came from Switch node path 1 (no cleanup needed)\n // This is determined by checking if we have Code node summary data\n const currentInput = $input.all();\n let codeNodeSummary = null;\n \n // Look for Code node summary in current input or previous nodes\n if (currentInput.length > 0 && currentInput[0].json && typeof currentInput[0].json.hasOldFolders !== 'undefined') {\n codeNodeSummary = currentInput[0].json;\n }\n \n if (codeNodeSummary) {\n if (!codeNodeSummary.hasOldFolders) {\n // Switch went to path 1 (no cleanup needed)\n cleanupStatus = 'Not Needed';\n cleanupNote = `No old backup folders found (checked ${codeNodeSummary.totalFoldersChecked || 0} folders)`;\n cleanupIcon = '✅';\n cleanupColor = '#22c55e';\n } else {\n // Switch went to path 0 (cleanup was needed), but we're now in success email\n // This means Delete was successful\n // FIXED: Count actual deleted folders from current input (from Loop Over Items)\n const deletedFoldersCount = currentInput.filter(item => item.json && item.json.id && item.json.name).length;\n cleanupStatus = 'Completed';\n cleanupNote = `Successfully cleaned up ${deletedFoldersCount} old backup folder${deletedFoldersCount > 1 ? 's' : ''}`;\n cleanupIcon = '✅';\n cleanupColor = '#22c55e';\n }\n } else {\n // Fallback: try to safely check other nodes\n try {\n // Try Code node directly (might be available)\n const codeResults = $('Filter backup folder').all();\n if (codeResults.length > 0 && codeResults[0].json) {\n const summary = codeResults[0].json;\n if (!summary.hasOldFolders) {\n cleanupStatus = 'Not Needed';\n cleanupNote = 'No old folders found';\n cleanupIcon = '✅';\n cleanupColor = '#22c55e';\n } else {\n // FIXED: Use the correct oldFoldersCount from Code node summary\n cleanupStatus = 'Completed'; \n cleanupNote = `Cleaned up ${summary.oldFoldersCount || 1} old folder${(summary.oldFoldersCount || 1) > 1 ? 's' : ''}`;\n cleanupIcon = '✅';\n cleanupColor = '#22c55e';\n }\n }\n } catch (e) {\n // Code node not accessible, use generic status\n cleanupStatus = 'Unknown';\n cleanupNote = 'Cleanup status could not be determined from current execution path';\n cleanupIcon = '❓';\n cleanupColor = '#6b7280';\n }\n }\n \n} catch (e) {\n console.log('Error determining cleanup status:', e);\n cleanupStatus = 'Unknown';\n cleanupNote = 'Error determining cleanup status: ' + e.message;\n cleanupIcon = '❓';\n cleanupColor = '#6b7280';\n}\n\n// Create beautiful enhanced HTML email\nconst htmlBody = `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>n8n Backup Success</title>\n <style>\n body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background-color: #f5f7fa; }\n .container { max-width: 900px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); overflow: hidden; }\n .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; }\n .header h1 { margin: 0; font-size: 32px; font-weight: 300; }\n .success-badge { background: #10b981; color: white; padding: 10px 20px; border-radius: 25px; display: inline-block; margin-top: 15px; font-weight: 600; font-size: 16px; }\n .content { padding: 35px; }\n .summary { background: #f8fafc; border-radius: 12px; padding: 25px; margin-bottom: 30px; }\n .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; margin-bottom: 25px; }\n .stat-card { background: white; border: 1px solid #e5e7eb; border-radius: 10px; padding: 20px; text-align: center; transition: transform 0.2s; }\n .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }\n .stat-number { font-size: 28px; font-weight: bold; color: #374151; margin-bottom: 8px; }\n .stat-label { color: #6b7280; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; }\n .stat-sublabel { color: #9ca3af; font-size: 12px; margin-top: 4px; }\n .insights-section { background: #eff6ff; border-radius: 12px; padding: 25px; margin-bottom: 30px; }\n .insight-item { background: white; border-radius: 8px; padding: 15px; margin-bottom: 10px; border-left: 4px solid #3b82f6; }\n .insight-item:last-child { margin-bottom: 0; }\n .workflows-table { width: 100%; border-collapse: collapse; margin-top: 20px; }\n .workflows-table th { background: #f9fafb; color: #374151; padding: 14px 12px; text-align: left; font-weight: 600; border-bottom: 2px solid #e5e7eb; font-size: 14px; }\n .workflows-table td { padding: 12px; border-bottom: 1px solid #f3f4f6; font-size: 14px; }\n .workflows-table tr:hover { background-color: #f9fafb; }\n .status-active { color: #10b981; font-weight: 600; }\n .status-inactive { color: #6b7280; }\n .size-large { color: #f59e0b; font-weight: 600; }\n .size-normal { color: #6b7280; }\n .footer { background: #f9fafb; padding: 25px 35px; color: #6b7280; font-size: 14px; text-align: center; }\n .folder-name { color: #4f46e5; font-weight: 600; }\n .backup-info { background: #eff6ff; border-left: 4px solid #3b82f6; padding: 20px; margin-bottom: 25px; border-radius: 0 8px 8px 0; }\n .cleanup-info { background: ${cleanupStatus === 'Failed' ? '#fee2e2' : cleanupStatus === 'Partially Completed' ? '#fef3c7' : cleanupStatus === 'Unknown' ? '#f3f4f6' : '#f0fdf4'}; border-left: 4px solid ${cleanupColor}; padding: 15px; margin-top: 15px; border-radius: 0 6px 6px 0; font-size: 14px; }\n @media (max-width: 600px) {\n .stats-grid { grid-template-columns: repeat(2, 1fr); }\n .container { margin: 10px; }\n .content { padding: 25px; }\n .workflows-table th, .workflows-table td { padding: 8px; font-size: 13px; }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>🛡️ n8n Backup Complete</h1>\n <div class=\"success-badge\">✅ BACKUP SUCCESSFUL</div>\n </div>\n \n <div class=\"content\">\n <div class=\"backup-info\">\n <strong>📁 Backup Folder:</strong> <span class=\"folder-name\">${folderName}</span><br>\n <strong>📅 Backup Date:</strong> ${backupDate.toFormat('DDDD')}<br>\n <strong>⏰ Backup Time:</strong> ${backupDate.toFormat('HH:mm:ss ZZZZ')}<br>\n <strong>🏢 Instance:</strong> n8n\n <div class=\"cleanup-info\">\n ${cleanupIcon} <strong>Cleanup Status:</strong> ${cleanupStatus}<br>\n <span style=\"font-size: 13px; color: #4b5563;\">${cleanupNote}</span>\n </div>\n </div>\n \n <div class=\"summary\">\n <h2 style=\"margin-top: 0; color: #374151; font-size: 24px; margin-bottom: 20px;\">📊 Backup Summary</h2>\n <div class=\"stats-grid\">\n <div class=\"stat-card\">\n <div class=\"stat-number\">${totalWorkflows}</div>\n <div class=\"stat-label\">Total Workflows</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-number\">${activeWorkflows}</div>\n <div class=\"stat-label\">Active Workflows</div>\n <div class=\"stat-sublabel\">${totalWorkflows > 0 ? Math.round((activeWorkflows/totalWorkflows)*100) : 0}% of total</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-number\">${inactiveWorkflows}</div>\n <div class=\"stat-label\">Inactive Workflows</div>\n <div class=\"stat-sublabel\">${totalWorkflows > 0 ? Math.round((inactiveWorkflows/totalWorkflows)*100) : 0}% of total</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-number\">${Math.round(totalSizeKB * 100) / 100} KB</div>\n <div class=\"stat-label\">Total Backup Size</div>\n <div class=\"stat-sublabel\">Avg: ${averageWorkflowSize} KB per workflow</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-number\" style=\"color: ${cleanupColor};\">${cleanupIcon}</div>\n <div class=\"stat-label\">Cleanup Status</div>\n <div class=\"stat-sublabel\">${cleanupStatus}</div>\n </div>\n ${recentlyModifiedCount > 0 ? `\n <div class=\"stat-card\">\n <div class=\"stat-number\">${recentlyModifiedCount}</div>\n <div class=\"stat-label\">Recently Modified</div>\n <div class=\"stat-sublabel\">Last 7 days</div>\n </div>\n ` : ''}\n ${largeWorkflowsCount > 0 ? `\n <div class=\"stat-card\">\n <div class=\"stat-number\">${largeWorkflowsCount}</div>\n <div class=\"stat-label\">Large Workflows</div>\n <div class=\"stat-sublabel\">> 50 KB</div>\n </div>\n ` : ''}\n </div>\n </div>\n \n ${recentlyModifiedCount > 0 || largeWorkflowsCount > 0 ? `\n <div class=\"insights-section\">\n <h3 style=\"margin-top: 0; color: #374151; font-size: 18px; margin-bottom: 15px;\">💡 Backup Insights</h3>\n ${recentlyModifiedCount > 0 ? `\n <div class=\"insight-item\">\n <strong>🔄 Recent Activity:</strong> ${recentlyModifiedCount} workflow${recentlyModifiedCount > 1 ? 's have' : ' has'} been modified in the last 7 days, indicating active development.\n </div>\n ` : ''}\n ${largeWorkflowsCount > 0 ? `\n <div class=\"insight-item\">\n <strong>📈 Large Workflows:</strong> ${largeWorkflowsCount} workflow${largeWorkflowsCount > 1 ? 's are' : ' is'} larger than 50KB, which may indicate complex automation processes.\n </div>\n ` : ''}\n ${activeWorkflows > 0 ? `\n <div class=\"insight-item\">\n <strong>⚡ Active Operations:</strong> ${activeWorkflows} active workflow${activeWorkflows > 1 ? 's are' : ' is'} currently running and processing data automatically.\n </div>\n ` : ''}\n </div>\n ` : ''}\n \n <h3 style=\"color: #374151; margin-bottom: 15px;\">📋 Backed Up Workflows</h3>\n <div style=\"overflow-x: auto;\">\n <table class=\"workflows-table\">\n <thead>\n <tr>\n <th>Workflow Name</th>\n <th>Status</th>\n <th>Last Updated</th>\n <th>Size (KB)</th>\n </tr>\n </thead>\n <tbody>\n ${workflowList.map(wf => `\n <tr>\n <td style=\"font-weight: 500;\">${wf.name}</td>\n <td class=\"${wf.active ? 'status-active' : 'status-inactive'}\">\n ${wf.active ? '🟢 Active' : '⚪ Inactive'}\n </td>\n <td>${wf.updatedAt}</td>\n <td class=\"${wf.size > 50 ? 'size-large' : 'size-normal'}\">\n ${wf.size} KB ${wf.size > 50 ? '📊' : ''}\n </td>\n </tr>\n `).join('')}\n </tbody>\n </table>\n </div>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin-bottom: 10px;\">🚀 <strong>Automated backup by n8n</strong></p>\n <p style=\"margin-bottom: 10px;\">📅 Next backup scheduled for <strong>tomorrow at 01:00 AM</strong></p>\n <p style=\"margin-bottom: 10px;\">☁️ This backup has been safely stored in <strong>Microsoft OneDrive</strong></p>\n <p style=\"margin: 0; font-size: 12px; color: #9ca3af;\">\n Backup ID: ${folderName} | Generated: ${backupDate.toISO()}\n </p>\n </div>\n </div>\n</body>\n</html>\n`;\n\n// Debug logging\nconsole.log('Safe Email Debug Info:');\nconsole.log('Total workflows:', totalWorkflows);\nconsole.log('Active workflows:', activeWorkflows);\nconsole.log('Cleanup status:', cleanupStatus, '(' + cleanupNote + ')');\nconsole.log('Uploaded files:', uploadedFiles.length);\nconsole.log('Current input length:', $input.all().length);\n\nreturn [{\n json: {\n htmlEmailBody: htmlBody,\n subject: `🛡️ n8n Daily Backup Success - ${totalWorkflows} workflows backed up (${Math.round(totalSizeKB * 100) / 100} KB)`,\n summary: {\n totalWorkflows,\n activeWorkflows,\n inactiveWorkflows,\n totalSizeKB: Math.round(totalSizeKB * 100) / 100,\n averageWorkflowSize,\n recentlyModifiedCount,\n largeWorkflowsCount,\n backupDate: backupDate.toISO(),\n folderName,\n cleanupStatus,\n cleanupNote,\n cleanupIcon\n },\n workflowList,\n insights: {\n recentlyModified: workflowsByStatus.recentlyModified.length,\n largeWorkflows: workflowsByStatus.large.length,\n activePercentage: totalWorkflows > 0 ? Math.round((activeWorkflows/totalWorkflows)*100) : 0\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "d846405f-6dde-40a1-b96f-e23225b59eb6",
"name": "每日定时触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-5520,
96
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 1
}
]
}
},
"typeVersion": 1.2
},
{
"id": "7e961954-d2ed-4a9d-8725-d444a1868c9f",
"name": "发送 HTML 成功邮件",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
-80,
112
],
"webhookId": "cdac200b-7c74-455c-b68d-fda0885fac67",
"parameters": {
"subject": "={{ $json.subject }}",
"bodyContent": "={{ $json.htmlEmailBody }}",
"additionalFields": {
"bodyContentType": "html"
}
},
"typeVersion": 2
},
{
"id": "e7575c54-1134-45bc-a4ed-fe88d5d27634",
"name": "搜索文件夹",
"type": "n8n-nodes-base.microsoftOneDrive",
"disabled": true,
"position": [
-5216,
-272
],
"parameters": {
"query": "Daily",
"resource": "folder",
"operation": "search"
},
"typeVersion": 1
},
{
"id": "d9b6048b-49bb-4e6b-97e9-8fb0bd2b122a",
"name": "切换旧文件夹",
"type": "n8n-nodes-base.switch",
"position": [
-2352,
96
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "False",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4efc2685-d40a-4bcd-bb19-40a50cc80b13",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.hasOldFolders }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "True",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "832f5561-39f3-4786-8842-0adc8071ecaa",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.hasOldFolders }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "df14e3ce-8a86-4c1d-a7a3-405f40f90d6b",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-1776,
240
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "af00819c-d885-4021-b693-06b9fb102112",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
-1008,
112
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "83e234c1-9eb7-48dc-8956-a818fa338a8f",
"name": "创建新备份文件夹",
"type": "n8n-nodes-base.microsoftOneDrive",
"position": [
-5056,
96
],
"parameters": {
"name": "=n8n_{{$now.format('yyyy-MM-dd')}}",
"options": {
"parentFolderId": ""
},
"resource": "folder",
"operation": "create"
},
"typeVersion": 1
},
{
"id": "b1fda1d4-c8b6-4882-95a2-048400064677",
"name": "转换为 JSON 文件",
"type": "n8n-nodes-base.convertToFile",
"position": [
-4160,
96
],
"parameters": {
"mode": "each",
"options": {
"format": true,
"fileName": "={{ $json.name }}"
},
"operation": "toJson"
},
"typeVersion": 1.1
},
{
"id": "ea490d15-f4d0-485e-8686-deb9044012b6",
"name": "上传 JSON 到文件夹",
"type": "n8n-nodes-base.microsoftOneDrive",
"position": [
-3712,
96
],
"parameters": {
"parentId": "={{ $('Create new backup folder').item.json.id }}",
"binaryData": true
},
"retryOnFail": false,
"typeVersion": 1
},
{
"id": "21d3de22-1836-49cd-99b7-1fafb710294e",
"name": "过滤备份文件夹",
"type": "n8n-nodes-base.code",
"position": [
-2848,
96
],
"parameters": {
"jsCode": "// Filter backup folders older than 31 days - Clean version\nconst inputItems = $input.all();\nconst cutoffDate = DateTime.now().minus({ days: 31 });\n\nconsole.log('Filtering backup folders older than 31 days');\nconsole.log('Cutoff date:', cutoffDate.toISO());\nconsole.log('Total folders found:', inputItems.length);\n\n// Filter folders older than 8 days\nconst oldFolders = [];\n\n// Process each input item\ninputItems.forEach((item, index) => {\n try {\n if (item.json.lastModifiedDateTime) {\n const folderDate = DateTime.fromISO(item.json.lastModifiedDateTime);\n const isOld = folderDate < cutoffDate;\n \n console.log('Folder:', item.json.name || 'Unknown', 'Date:', folderDate.toFormat('dd/MM/yyyy'), 'Old:', isOld);\n \n if (isOld) {\n oldFolders.push(item);\n }\n }\n } catch (error) {\n console.log('Error parsing date for folder:', item.json.name, error);\n }\n});\n\nconsole.log('Old folders to delete:', oldFolders.length);\nconsole.log('Recent folders to keep:', inputItems.length - oldFolders.length);\n\nif (oldFolders.length > 0) {\n // FOUND OLD FOLDERS TO DELETE\n // Return ONLY the old folders with summary info in first item\n const result = oldFolders.map((folder, index) => {\n if (index === 0) {\n // First old folder gets summary metadata\n return {\n json: {\n id: folder.json.id,\n name: folder.json.name,\n lastModifiedDateTime: folder.json.lastModifiedDateTime,\n createdDateTime: folder.json.createdDateTime,\n size: folder.json.size,\n // Add metadata\n hasOldFolders: true,\n oldFoldersCount: oldFolders.length,\n totalFoldersChecked: inputItems.length,\n cutoffDate: cutoffDate.toISO()\n }\n };\n } else {\n // Other old folders keep original structure\n return {\n json: {\n id: folder.json.id,\n name: folder.json.name,\n lastModifiedDateTime: folder.json.lastModifiedDateTime,\n createdDateTime: folder.json.createdDateTime,\n size: folder.json.size\n }\n };\n }\n });\n \n console.log('Returning', result.length, 'OLD folders for deletion');\n return result;\n \n} else {\n // NO OLD FOLDERS FOUND\n // Return single summary item\n console.log('No old folders found - all folders are recent');\n return [{\n json: {\n hasOldFolders: false,\n oldFoldersCount: 0,\n totalFoldersChecked: inputItems.length,\n cutoffDate: cutoffDate.toISO(),\n recentFoldersCount: inputItems.length,\n message: 'All backup folders are recent - no cleanup needed'\n }\n }];\n}"
},
"typeVersion": 2
},
{
"id": "ba663fbc-67f2-47e3-a237-122aa555f7d8",
"name": "便签 - 概览",
"type": "n8n-nodes-base.stickyNote",
"position": [
-6320,
128
],
"parameters": {
"color": 3,
"width": 600,
"height": 940,
"content": "## 🗂️ 自动化工作流备份管理器"
},
"typeVersion": 1
},
{
"id": "0a3c08c4-6a18-4755-be8d-fc45103959f0",
"name": "便签 - 发送 HTML 成功邮件",
"type": "n8n-nodes-base.stickyNote",
"position": [
-192,
304
],
"parameters": {
"color": 7,
"width": 380,
"height": 860,
"content": "## 📧 发送 HTML 成功邮件"
},
"typeVersion": 1
},
{
"id": "46ee0eac-d1e0-4948-ad60-f729abe72f3c",
"name": "便签 - 准备 HTML 邮件",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
304
],
"parameters": {
"color": 7,
"width": 380,
"height": 860,
"content": "## 📧 准备 HTML 邮件"
},
"typeVersion": 1
},
{
"id": "baec94aa-e4c1-4341-92aa-e787c084966e",
"name": "便签 - 合并",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1040,
304
],
"parameters": {
"color": 7,
"width": 380,
"height": 540,
"content": "## 🔗 合并"
},
"typeVersion": 1
},
{
"id": "7c8e9e6c-aa5e-4ecb-8669-f86051cce7ab",
"name": "便签 - 删除旧备份文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1504,
448
],
"parameters": {
"color": 7,
"width": 380,
"height": 668,
"content": "## 🗑️ 删除旧备份文件夹"
},
"typeVersion": 1
},
{
"id": "c56f7762-dcfe-4078-ba90-2d14e9a05b31",
"name": "便签 - 遍历项目",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
448
],
"parameters": {
"color": 7,
"width": 380,
"height": 620,
"content": "## 📁 遍历项目"
},
"typeVersion": 1
},
{
"id": "e4ccfaed-5fce-4b58-8757-f5883787b979",
"name": "便签 - 切换旧文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2464,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 732,
"content": "## 🔄 切换旧文件夹"
},
"typeVersion": 1
},
{
"id": "8801d8ea-a8c3-4e5d-bc7b-4c5ecd0a9822",
"name": "便签 - 过滤备份文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2976,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 876,
"content": "## 📁 过滤备份文件夹"
},
"typeVersion": 1
},
{
"id": "8b871fe0-f8f4-48c1-9954-4ddb0a835293",
"name": "便签 - 获取备份文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3392,
272
],
"parameters": {
"color": 4,
"width": 380,
"height": 748,
"content": "## 📁 获取备份文件夹"
},
"typeVersion": 1
},
{
"id": "05e566d8-2cf1-4d6a-ae96-8800a5ba3d43",
"name": "便签 - 上传 JSON 到文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3824,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 876,
"content": "## 📁 上传 JSON 到文件夹"
},
"typeVersion": 1
},
{
"id": "77768b3c-e607-4896-ba99-5f6ae71d40bc",
"name": "便签 - 转换为 JSON 文件",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4288,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 876,
"content": "## 📄 转换为 JSON 文件"
},
"typeVersion": 1
},
{
"id": "a60decc9-42fc-465d-a89f-f552e6a84814",
"name": "便签 - 检索工作流",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4736,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 876,
"content": "## 📁 检索工作流"
},
"typeVersion": 1
},
{
"id": "52972885-4bad-4a28-b02c-fd454d97a97f",
"name": "便签 - 搜索文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-5424,
-944
],
"parameters": {
"color": 2,
"width": 540,
"height": 860,
"content": "## 🔍 搜索文件夹"
},
"typeVersion": 1
},
{
"id": "cc5e9202-f9f3-4cac-a849-9d5f3febbf20",
"name": "便签 - 创建新备份文件夹",
"type": "n8n-nodes-base.stickyNote",
"position": [
-5184,
272
],
"parameters": {
"color": 4,
"width": 380,
"height": 924,
"content": "## 📁 创建新备份文件夹"
},
"typeVersion": 1
},
{
"id": "ea1105b6-5b0e-4ee6-b1d0-a8f210cce50b",
"name": "便签 - 每日定时触发器",
"type": "n8n-nodes-base.stickyNote",
"position": [
-5632,
272
],
"parameters": {
"color": 7,
"width": 380,
"height": 876,
"content": "## 📅 每日定时触发器"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "e5bb50a7-5d9c-42dd-b660-92172b5e49ae",
"connections": {
"Merge": {
"main": [
[
{
"node": "Prepare HTML Email",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
],
[
{
"node": "Delete old backup folder",
"type": "main",
"index": 0
}
]
]
},
"Get backupfolders": {
"main": [
[
{
"node": "Filter backup folder",
"type": "main",
"index": 0
}
]
]
},
"Prepare HTML Email": {
"main": [
[
{
"node": "Send HTML Success Email",
"type": "main",
"index": 0
}
]
]
},
"Retrieve workflows": {
"main": [
[
{
"node": "Convert to JSON file",
"type": "main",
"index": 0
}
]
]
},
"Switch Old Folders": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Convert to JSON file": {
"main": [
[
{
"node": "Upload JSON to folder",
"type": "main",
"index": 0
}
]
]
},
"Filter backup folder": {
"main": [
[
{
"node": "Switch Old Folders",
"type": "main",
"index": 0
}
]
]
},
"Upload JSON to folder": {
"main": [
[
{
"node": "Get backupfolders",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger Daily": {
"main": [
[
{
"node": "Create new backup folder",
"type": "main",
"index": 0
}
]
]
},
"Create new backup folder": {
"main": [
[
{
"node": "Retrieve workflows",
"type": "main",
"index": 0
}
]
]
},
"Delete old backup folder": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 开发运维
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
使用n8n API和邮件发送生成周度工作流分析报告
使用n8n API和邮件发送生成周度工作流分析报告
N8n
Set
Code
+6
19 节点Wessel Bulte
开发运维
高级 n8n 工作流与 GitHub 同步
使用 GitHub 的智能变更检测自动化工作流备份
If
N8n
Set
+10
38 节点Maksym Brashenko
开发运维
自动化n8n工作流备份至GitHub并追踪删除
自动化n8n工作流备份至GitHub并追踪删除
If
N8n
Set
+13
31 节点Marcial Ambriz
开发运维
将您的工作流保存到 GitHub 仓库
每日工作流备份至 GitHub 并发送 Slack 通知
If
N8n
Set
+10
18 节点Andrew
开发运维
GitHub 同步仪表板 - V2
具有提交历史和回滚功能的 GitHub 工作流版本控制仪表板
If
N8n
Set
+20
94 节点Eduard
开发运维
使用 TenderNed 自动化荷兰公共采购数据收集
使用 TenderNed 自动化荷兰公共采购数据收集
Xml
Code
Merge
+9
28 节点Wessel Bulte
市场调研
工作流信息
难度等级
高级
节点数量29
分类1
节点类型10
作者
Wessel Bulte
@uuesselCybersecurity and automation consultant specializing in n8n workflows for GDPR compliance, process optimization, and business integration. Helping teams streamline operations with secure, scalable automation solutions.
外部链接
在 n8n.io 查看 →
分享此工作流