8
n8n 中文网amn8n.com

备份n8n工作流到OneDrive

高级

这是一个DevOps领域的自动化工作流,包含 29 个节点。主要使用 N8n, Code, Merge, Switch, ConvertToFile 等节点。 自动备份n8n工作流到OneDrive,包含清理和邮件通知功能

前置要求
  • 无特殊前置要求,导入即可使用
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 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)可能需要您自行付费。

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

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

作者
Wessel Bulte

Wessel Bulte

@uuessel

Cybersecurity 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 查看

分享此工作流