高级 n8n 工作流与 GitHub 同步
高级
这是一个DevOps, Multimodal AI领域的自动化工作流,包含 38 个节点。主要使用 If, N8n, Set, Code, Merge 等节点。 使用 GitHub 的智能变更检测自动化工作流备份
前置要求
- •GitHub Personal Access Token
- •Telegram Bot Token
使用的节点 (38)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "dmvTNU9rfNdgTeSp",
"meta": {
"instanceId": "e7cc7f71b8002726158f14502c7243f892bdf0befb8af4790197437e4666e71e"
},
"name": "高级 n8n 工作流与 GitHub 同步",
"tags": [],
"nodes": [
{
"id": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-368,
912
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "34869a4d-b687-44f7-81fd-5f71e8679a0e",
"name": "更新文件内容并提交",
"type": "n8n-nodes-base.github",
"position": [
1584,
1872
],
"webhookId": "f2d754dd-b68d-41e8-a662-7e91c1c3aa95",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=update: {{ $json.context.newFile.name }}"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "bbf77bc7-8c35-4177-be4f-44c91682fd1a",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2912,
864
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "34d67ff9-e25a-42a9-b751-dbde37ed575b",
"name": "获取所有工作流",
"type": "n8n-nodes-base.n8n",
"position": [
-1328,
848
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "M9BEPZyx4jMbY5tY",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "98fc6907-d188-4f9a-b68e-44592bfac95b",
"name": "编码 N8N 工作流",
"type": "n8n-nodes-base.code",
"position": [
-1104,
848
],
"parameters": {
"jsCode": "// Encode workflow data to base64 to prevent data pollution\nconst items = $input.all();\n\nfor (const item of items) {\n const originalWorkflow = item.json;\n\n item.json = {\n id: originalWorkflow.id,\n name: originalWorkflow.name,\n n8nWorkflowData: Buffer.from(JSON.stringify(originalWorkflow)).toString('base64')\n };\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "3576b4b3-6692-4d08-9453-697d44ac0f56",
"name": "决策变更",
"type": "n8n-nodes-base.code",
"position": [
-112,
928
],
"parameters": {
"jsCode": "// Helper function to ensure stable JSON serialization for reliable comparison.\nfunction sortKeysDeep(obj) {\n if (obj === null || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj.map(sortKeysDeep);\n const out = {};\n Object.keys(obj).sort().forEach(k => { out[k] = sortKeysDeep(obj[k]); });\n return out;\n}\n\nconst items = $input.all();\nconst WORKFLOWS_DIR = $node[\"Configuration\"].json.repo.path;\n\nfor (const item of items) {\n const src = item.json || {};\n const flags = {\n fileExists: false,\n nameChanged: false,\n shouldCommit: false\n };\n // Initialize the context container\n item.json.context = {\n oldFile: { path: '', name: '' },\n newFile: { path: '', name: '' },\n operation: ''\n };\n const context = item.json.context;\n\n // 1. Determine if the file exists on GitHub.\n const hasGithub = typeof src.githubWorkflowData === 'string' && src.githubWorkflowData.length > 0;\n flags.fileExists = hasGithub;\n\n // 2. Extract the current workflow name from the N8N data.\n const currentName = src.name || '';\n context.newFile.name = currentName;\n\n // 3. Detect renames and set file paths.\n if (typeof src.filePath === 'string' && src.filePath.length > 0) {\n const parts = src.filePath.split('/');\n const filename = parts.pop() || '';\n const githubName = filename.endsWith('.json') ? filename.slice(0, -5) : filename;\n \n flags.nameChanged = githubName !== currentName;\n context.oldFile.path = src.filePath;\n context.oldFile.name = githubName;\n \n const dirPath = parts.join('/');\n context.newFile.path = `${dirPath}/${currentName}.json`;\n\n } else {\n flags.nameChanged = false;\n context.newFile.path = `${WORKFLOWS_DIR}/${currentName}.json`.replace(/\\/+/g, '/');\n }\n\n // 4. Perform a stable comparison to see if a commit is needed.\n try {\n if (flags.fileExists) {\n const n8nJsonStr = Buffer.from(src.n8nWorkflowData, 'base64').toString('utf8');\n const githubJsonStr = Buffer.from(src.githubWorkflowData, 'base64').toString('utf8');\n const n8nObj = JSON.parse(n8nJsonStr);\n const githubObj = JSON.parse(githubJsonStr);\n const stableN8nStr = JSON.stringify(sortKeysDeep(n8nObj));\n const stableGithubStr = JSON.stringify(sortKeysDeep(githubObj));\n flags.shouldCommit = stableN8nStr !== stableGithubStr;\n } else {\n flags.shouldCommit = true; // New file, always commit.\n }\n } catch (e) {\n flags.shouldCommit = true; // If parsing or comparison fails, better to commit.\n }\n\n // 5. Determine the final operation type.\n if (flags.nameChanged) {\n context.operation = 'rename';\n } else if (!flags.fileExists) {\n context.operation = 'create';\n } else if (flags.shouldCommit) {\n context.operation = 'update';\n } else {\n context.operation = 'skip';\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "1cc5a35b-8e52-46dd-ac69-03e320f66fc8",
"name": "删除旧文件",
"type": "n8n-nodes-base.github",
"position": [
1744,
1408
],
"webhookId": "1bd59af3-c8ee-4664-9cfd-df3ab4b6793d",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.oldFile.path }}",
"resource": "file",
"operation": "delete",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 1/2: remove old)"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "c6464638-d7d4-4e36-b359-f70412db2bbb",
"name": "创建新文件(重命名)",
"type": "n8n-nodes-base.github",
"position": [
2144,
1408
],
"webhookId": "1b7a1463-d11a-4c0c-a596-a4b09e003d5a",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 2/2: create new)"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "b470dd47-762a-4c8a-90e0-e4f535e9b41e",
"name": "创建后合并(重命名)",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
2384,
1392
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "be267762-77a4-4af5-ba26-7f3945d44b32",
"name": "更新后合并",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1872,
1856
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "db1f95ae-42e5-4588-bb7d-5348011d9afe",
"name": "列出文件",
"type": "n8n-nodes-base.github",
"notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
"onError": "continueErrorOutput",
"position": [
-1552,
1040
],
"webhookId": "2e1f9567-52d4-4047-980c-6b4a57d4bd40",
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $(\"Configuration\").item.json.repo.path }}",
"resource": "file",
"operation": "list",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
}
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1.1
},
{
"id": "8c6f381b-caed-4267-abb4-6672c93f45e9",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
-880,
928
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "8d8f0963-0b37-47d9-b263-ffc7da06d118",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
1680,
1168
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 重命名文件(两步法)"
},
"typeVersion": 1
},
{
"id": "371212bd-c19c-4908-b3cb-fe75034a95fb",
"name": "便签 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
752
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 收集数据"
},
"typeVersion": 1
},
{
"id": "fb300a39-10b0-48cf-a793-a96e1ee3014f",
"name": "便签 2",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
-2672,
592
],
"parameters": {
"color": 4,
"width": 326,
"height": 448,
"content": "## 设置参数"
},
"typeVersion": 1
},
{
"id": "80dabce7-e36e-4ea7-bd7d-67fb3b3096f8",
"name": "便签 3",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
-3024,
592
],
"parameters": {
"color": 4,
"width": 326,
"height": 448,
"content": "## 调整计划"
},
"typeVersion": 1
},
{
"id": "802494dc-8c76-4a34-97d3-c414cb04a179",
"name": "便签 4",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
176,
0
],
"parameters": {
"color": 7,
"width": 1174,
"height": 272,
"content": "## 执行报告"
},
"typeVersion": 1
},
{
"id": "d0af7c02-a586-4993-8c4b-8fb18c3053fe",
"name": "构建摘要数组",
"type": "n8n-nodes-base.code",
"position": [
480,
112
],
"parameters": {
"jsCode": "// Aggregate arrays and flags for summary (no rendering)\n\nfunction normalizeName(item) {\n return String(\n item.json?.name || 'unknown'\n );\n}\n\nconst items = $input.all();\n\nconst buckets = { create: [], update: [], rename: [], skip: [] };\n\nfor (const it of items) {\n const name = normalizeName(it);\n const op = it.json?.context.operation;\n const oldName = it.json?.context.oldFile.name || name;\n const newName = it.json?.context.newFile.name || name;\n\n if (op === 'rename') buckets.rename.push(`${oldName} -> ${newName}`);\n else if (op === 'create') buckets.create.push(name);\n else if (op === 'update') buckets.update.push(name);\n else buckets.skip.push(name);\n}\n\nconst isAnythingChanged = buckets.create.length > 0 || buckets.update.length > 0 || buckets.rename.length > 0;\n\nreturn [{ json: {\n isAnythingChanged,\n created: buckets.create,\n updated: buckets.update,\n renamed: buckets.rename,\n skipped: buckets.skip\n} }];"
},
"typeVersion": 2
},
{
"id": "0c401771-b522-4091-a14f-aba4a06d6158",
"name": "渲染摘要",
"type": "n8n-nodes-base.code",
"notes": "## Connecting to a messenger\n\nUse {{$json.message}} as message text. 'isAnythingChanged' controls whether to send.",
"position": [
928,
112
],
"parameters": {
"jsCode": "// Helper function to escape text for Telegram's MarkdownV2 parser\nconst escapeMarkdownV2 = (str) => {\n // For the full list of characters, see https://core.telegram.org/bots/api#markdownv2-style\n return String(str).replace(/([_\\[\\]()~`>#+=|{}.!*-])/g, '\\\\$1');\n};\n\nconst all = $input.all();\nconst data = (all[0] && all[0].json) || {};\n\nconst config = $('Configuration').first().json;\nconst repoOwner = config.repo.owner;\nconst repoName = config.repo.name;\nconst repoPath = config.repo.path;\n\n// Construct repository URL using /blob/-/ for a branch-agnostic link\nconst repoUrl = `https://github.com/${repoOwner}/${repoName}/blob/-/${repoPath}`;\n\n// The link's *text* must be escaped, but the URL must not be.\nconst repoLinkText = escapeMarkdownV2(`${repoOwner}/${repoName}/${repoPath}`);\nconst repoLink = `[${repoLinkText}](${repoUrl})`;\n\nconst getList = key => (Array.isArray(data[key]) ? data[key] : []);\nconst sortAsc = (a, b) => String(a).localeCompare(String(b));\n\nconst sections = [\n { key: 'created', title: 'Created' },\n { key: 'updated', title: 'Updated' },\n { key: 'renamed', title: 'Renamed' },\n { key: 'skipped', title: 'Skipped (no changes)' },\n];\n\nconst summaryParts = [\n `created ${getList('created').length}`,\n `updated ${getList('updated').length}`,\n `renamed ${getList('renamed').length}`,\n `skipped ${getList('skipped').length}`\n];\n\n// Construct the final message with the new header format\nconst messageLines = [\n '*Backup N8N workflows to GitHub*', // Main title\n '', // Blank line for spacing\n `Repo: ${repoLink}`,\n `Totals: ${escapeMarkdownV2(summaryParts.join(', '))}`\n];\n\n// Append detailed lists as before\nfor (const { key, title } of sections) {\n const list = [...getList(key)].sort(sortAsc);\n if (list.length) {\n messageLines.push('', `*${escapeMarkdownV2(title)}:*`);\n for (const item of list) {\n let line;\n if (key === 'renamed') {\n const [oldName, newName] = item.split(' -> ');\n line = `\\`${oldName}.json\\` ${escapeMarkdownV2('->')} \\`${newName}.json\\``;\n } else {\n line = `\\`${item}.json\\``;\n }\n messageLines.push(line);\n }\n }\n}\n\nconst message = messageLines.join('\\n');\nreturn [{ json: { message } }];"
},
"notesInFlow": false,
"typeVersion": 2
},
{
"id": "fdb5e4f7-8aa1-4273-8776-ac0248b893e4",
"name": "是否有更改?",
"type": "n8n-nodes-base.if",
"position": [
704,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "8148a3ce-16fe-4074-9f57-c49072be8a8f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $node[\"Configuration\"].json.report.verbose }}",
"rightValue": ""
},
{
"id": "ee4ef204-341f-444a-a26a-299aa0cde573",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.isAnythingChanged }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "13ba1e58-2bac-44dc-9142-b61efef7c4e1",
"name": "提取工作流参数",
"type": "n8n-nodes-base.code",
"position": [
-1104,
1040
],
"parameters": {
"jsCode": "let items = $input.all();\n\nfor (let item of items) {\n try {\n const contentBase64 = item.json.content;\n const path = item.json.path;\n const sha = item.json.sha;\n\n // Decode and parse GitHub file content to extract name\n const content = Buffer.from(contentBase64, 'base64').toString('utf8');\n const workflow = JSON.parse(content);\n\n // Keep only the fields we need from GitHub side, store as base64\n item.json = {\n id: workflow.id,\n name: workflow.name,\n filePath: path,\n githubWorkflowData: contentBase64, // Store as base64 to match N8N side\n sha: sha\n };\n\n } catch (error) {\n // Non-JSON or invalid workflow file\n console.log(`Error parsing file ${item.json.path}: ${error.message}`);\n item.json = {\n id: null,\n name: null,\n filePath: item.json.path,\n error: error.message\n };\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "3464840a-599d-4432-aade-bfd5a2930461",
"name": "获取文件",
"type": "n8n-nodes-base.github",
"notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
"onError": "continueErrorOutput",
"position": [
-1328,
1040
],
"webhookId": "93c8a2dd-ddad-4837-a062-25473eee1208",
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.path }}",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1.1
},
{
"id": "d991e89f-d795-4559-9b3f-4ecce50723b5",
"name": "是否配置了 Telegram?",
"type": "n8n-nodes-base.if",
"position": [
256,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "03191658-7c7b-4f85-a07b-35d9749d91f3",
"operator": {
"type": "number",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
"rightValue": 0
},
{
"id": "3277a7ef-0895-4fbf-beb2-432f54cc8efc",
"operator": {
"type": "number",
"operation": "notEquals"
},
"leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0705a90f-00e1-46e6-b5ef-4a4efe5255cd",
"name": "发送消息",
"type": "n8n-nodes-base.telegram",
"position": [
1152,
112
],
"webhookId": "ea0e343d-7ffa-4dd7-a3aa-0e7e45ad5753",
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $node[\"Configuration\"].json.report.tg.chatID }}",
"additionalFields": {
"parse_mode": "MarkdownV2",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "7sLcbl1lRhPnfzJI",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "fdef22d8-7960-48e2-a046-e69b3f48ce15",
"name": "创建新文件",
"type": "n8n-nodes-base.github",
"position": [
1296,
2416
],
"webhookId": "66429ae4-4b7d-4fb5-8438-26cdf6c4faa8",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=create: {{ $json.context.newFile.name }}"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "669101cb-2d23-41c7-8e07-6e282b556cdd",
"name": "创建后合并",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1584,
2400
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "c5d3b5e4-9c2a-4cf8-8bae-fca50b9f884c",
"name": "便签 5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1328,
1712
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 更新现有文件"
},
"typeVersion": 1
},
{
"id": "d58b3267-9510-4abd-b4df-14f5cc19f984",
"name": "便签6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1040,
2256
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 创建新文件"
},
"typeVersion": 1
},
{
"id": "2ababf2c-5315-4d6a-9f09-b2efaef4ab2c",
"name": "无操作,不执行任何操作",
"type": "n8n-nodes-base.noOp",
"position": [
1056,
2976
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7e069ed2-7303-4db5-b0b3-f18edbfb88da",
"name": "便签7",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
2800
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 无需操作"
},
"typeVersion": 1
},
{
"id": "0ac9743d-298a-49d2-b391-4c34b9d5122b",
"name": "停止并报错",
"type": "n8n-nodes-base.stopAndError",
"position": [
336,
3376
],
"parameters": {
"errorMessage": "=Invalid operation: \"{{ $json.context.operation }}\". You should look at the code in the \"Decide changes\" node."
},
"typeVersion": 1
},
{
"id": "f7a498d9-900f-41ab-9b5f-8e849b85c38c",
"name": "便签8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
752
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## 控制器"
},
"typeVersion": 1
},
{
"id": "4d426ef2-c4db-4690-bd23-48c374fcf0e1",
"name": "路由器",
"type": "n8n-nodes-base.switch",
"position": [
128,
880
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "=rename",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5af9aafc-3ee1-4855-89b2-b0ceb83b3169",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "rename"
}
]
},
"renameOutput": true
},
{
"outputKey": "update",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "849881fc-2d0e-4154-b6c2-10ff6c2b5480",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "update"
}
]
},
"renameOutput": true
},
{
"outputKey": "create",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "85f4cce5-476e-4970-82b4-0b04cc67870f",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "create"
}
]
},
"renameOutput": true
},
{
"outputKey": "skip",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "51919025-e488-4557-9cd9-23f4be9bbf06",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "skip"
}
]
},
"renameOutput": true
}
]
},
"options": {
"ignoreCase": true,
"fallbackOutput": "extra",
"renameFallbackOutput": "error"
}
},
"typeVersion": 3.2
},
{
"id": "921826cc-13da-4db8-8a18-fdffacbcf5bb",
"name": "删除后合并(重命名)",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1968,
1248
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "bda0d23c-f6fe-435c-8732-c1f97c313d80",
"name": "配置",
"type": "n8n-nodes-base.set",
"position": [
-2576,
864
],
"parameters": {
"values": {
"number": [
{
"name": "report.tg.chatID",
"value": null
}
],
"string": [
{
"name": "repo.owner"
},
{
"name": "repo.name"
},
{
"name": "repo.path",
"value": "workflows/"
}
],
"boolean": [
{
"name": "report.verbose"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "a9074602-25a9-4fec-a464-caf1459dd3fb",
"name": "便签 10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3024,
1072
],
"parameters": {
"width": 672,
"height": 960,
"content": "## 高级 n8n 工作流与 GitHub 同步"
},
"typeVersion": 1
},
{
"id": "999b6d5e-ba67-4b98-9637-1ca8c061113e",
"name": "空配置时停止",
"type": "n8n-nodes-base.stopAndError",
"position": [
-1920,
1024
],
"parameters": {
"errorMessage": "Incomplete GitHub configuration. Please check \"Configuration\" node."
},
"typeVersion": 1
},
{
"id": "99820b4c-3ad8-43ba-bafe-9ae5f919e39f",
"name": "断言 GitHub 配置",
"type": "n8n-nodes-base.if",
"notes": "Pre-provisioning safe fuse",
"position": [
-2160,
864
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0b285299-edd5-41a0-85e8-3d94246e1cff",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.owner }}",
"rightValue": ""
},
{
"id": "c9f894e0-cf42-45e1-87bd-13c2bd024b48",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.name }}",
"rightValue": ""
},
{
"id": "f1591996-df67-4caf-8171-a049993268d2",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.path }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "02eb2192-2fce-4283-90c0-616f2e6ced5d",
"connections": {
"Merge": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Router": {
"main": [
[
{
"node": "Delete old file",
"type": "main",
"index": 0
},
{
"node": "Merge after delete (rename)",
"type": "main",
"index": 0
}
],
[
{
"node": "Update file content and commit",
"type": "main",
"index": 0
},
{
"node": "Merge after update",
"type": "main",
"index": 0
}
],
[
{
"node": "Create new file",
"type": "main",
"index": 0
},
{
"node": "Merge after create",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "Stop and Error",
"type": "main",
"index": 0
}
]
]
},
"Get files": {
"main": [
[
{
"node": "Extract workflow parameters",
"type": "main",
"index": 0
}
]
]
},
"List files": {
"main": [
[
{
"node": "Get files",
"type": "main",
"index": 0
}
]
]
},
"Configuration": {
"main": [
[
{
"node": "Assert GitHub config",
"type": "main",
"index": 0
}
]
]
},
"Decide changes": {
"main": [
[
{
"node": "Router",
"type": "main",
"index": 0
}
]
]
},
"Render summary": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Create new file": {
"main": [
[
{
"node": "Merge after create",
"type": "main",
"index": 1
}
]
]
},
"Delete old file": {
"main": [
[
{
"node": "Merge after delete (rename)",
"type": "main",
"index": 1
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Is Telegram configured?",
"type": "main",
"index": 0
}
],
[
{
"node": "Decide changes",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
},
"Anything changed?": {
"main": [
[
{
"node": "Render summary",
"type": "main",
"index": 0
}
]
]
},
"Get all workflows": {
"main": [
[
{
"node": "Encode N8N workflows",
"type": "main",
"index": 0
}
]
]
},
"Merge after create": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Merge after update": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Assert GitHub config": {
"main": [
[
{
"node": "Get all workflows",
"type": "main",
"index": 0
},
{
"node": "List files",
"type": "main",
"index": 0
}
],
[
{
"node": "Stop on empty config",
"type": "main",
"index": 0
}
]
]
},
"Build summary arrays": {
"main": [
[
{
"node": "Anything changed?",
"type": "main",
"index": 0
}
]
]
},
"Encode N8N workflows": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Is Telegram configured?": {
"main": [
[
{
"node": "Build summary arrays",
"type": "main",
"index": 0
}
]
]
},
"Create new file (rename)": {
"main": [
[
{
"node": "Merge after create (rename)",
"type": "main",
"index": 1
}
]
]
},
"No Operation, do nothing": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Extract workflow parameters": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Merge after create (rename)": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Merge after delete (rename)": {
"main": [
[
{
"node": "Merge after create (rename)",
"type": "main",
"index": 0
},
{
"node": "Create new file (rename)",
"type": "main",
"index": 0
}
]
]
},
"Update file content and commit": {
"main": [
[
{
"node": "Merge after update",
"type": "main",
"index": 1
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 开发运维, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
GitHub 同步仪表板 - V2
具有提交历史和回滚功能的 GitHub 工作流版本控制仪表板
If
N8n
Set
+20
94 节点Eduard
开发运维
自动化n8n工作流备份至GitHub并追踪删除
自动化n8n工作流备份至GitHub并追踪删除
If
N8n
Set
+13
31 节点Marcial Ambriz
开发运维
GPT-4驱动的冷邮件工作流,包含完全定制的3封邮件跟进
使用GPT-4、Mailgun和Supabase自动化个性化冷邮件序列
If
Set
Code
+22
100 节点Paul
客户培育
使用Google Drive、GitHub和消息警报的自动化工作流备份系统
使用Google Drive、GitHub和消息警报的自动化工作流备份系统
If
N8n
Set
+11
20 节点Khairul Muhtadin
内容创作
Typebot 流程与 GitHub 双向同步,使用 Typebot API
Typebot 流程与 GitHub 双向同步,使用 Typebot API
If
Set
Code
+12
31 节点Marcial Ambriz
开发运维
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
If
Set
Code
+26
116 节点Paul
内容创作
工作流信息
难度等级
高级
节点数量38
分类2
节点类型13
作者
Maksym Brashenko
@j2h4uI’m a generalist engineer who thrives on messy systems, undocumented protocols, and problems no one wants to touch Over the past 6 years, I’ve built infrastructure for managing hardware, reverse-engineered flaky devices, automated workflows end to end, and connected product logic to business reality Use my link to book an initial consultation
外部链接
在 n8n.io 查看 →
分享此工作流