iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Software Development

AI 驅動的 Code Review:MCP 與 n8n 自動化實踐系列 第 26

[Day 26] AI 驅動的 Code Review: n8n 模組匯出匯入與模板大公開

  • 分享至 

  • xImage
  •  

AI 驅動的 Code Review:n8n 模組匯出匯入與模板大公開

前言

有組長問我,如果他們的團隊也想導入這個流程,要怎麼做?
今天就來分享 n8n 模組的匯出與匯入功能,並公開我製作的 workflow 模板,供大家參考使用。

模組匯出與匯入

匯出

如果你想將自己寫好的範本分享給其他人使用,可以在 n8n workflow 右上角點選「...」,選擇 Download 來匯出。

https://ithelp.ithome.com.tw/upload/images/20250925/201214993jcJiiUBPw.png

匯入

我將自己花幾天整理的 workflow 分享給大家,以下是 workflow.json 範例:

  1. 將以下內容存成 workflow.json 檔案。
  2. 在 n8n workflow 右上角點選「...」,選擇 Import From File,即可產生我製作的模板。
{
  "name": "Gitlab Code Review- Prod",
  "nodes": [
    {
      "parameters": {
        "promptType": "define",
        "text": "=請你扮演一位資深軟體工程師,針對程式碼進行全面性的 Code Review。請依照以下面向給出具體建議:\n\n1. ✅ 程式邏輯是否正確、是否有潛在錯誤或邊界條件未處理\n2. 🧹 是否符合的慣用寫法,例如錯誤處理、命名風格、簡潔性\n3. 🔒 是否有安全性問題,例如硬編碼、未處理的錯誤、資源洩漏等\n4. 🚀 是否有效能瓶頸,例如不必要的記憶體分配、重複運算、goroutine 使用不當\n5. 📚 是否具備良好的可讀性與可維護性,包括註解、結構清晰度、模組化程度\n\n在評估每個檔案時,請遵循以下結構化步驟:\n1. 先逐個面向分析問題,並列出證據。\n2. 綜合所有問題,決定 severity 等級。\n3. 只在有明確證據時提升 severity,避免過度評估。\n\nseverity 等級的詳細定義(必須嚴格遵守):\n- high(高風險):問題可能導致系統崩潰、安全漏洞、資料洩漏、嚴重效能衰退,或在生產環境造成重大損失。例如:未處理的 nil 指標、SQL 注入風險、無限迴圈。\n- medium(中風險):問題可能引起偶發錯誤、非最佳實踐,但不立即危險。例如:未優化的迴圈、缺少錯誤檢查、輕微資源浪費。\n- low(低風險):小問題,如命名不一致、缺少註解、多餘空白,但不影響功能或效能。\n- no problem(無問題):不用輸出到 JSON,所有面向皆符合最佳實踐,無任何可改進點或者單元測試的 error 寫底線也是可以,放到 no problem 例如: req, _ := http.NewRequest(\"GET\", \"/ping\", nil)\n\n範例:\n假設程式碼片段:\nfunc add(a, b int) int { return a + b } // 簡單加法,無問題。\n- 分析:邏輯正確、無錯誤;慣用寫法符合;無安全問題;效能佳;可讀性高。\n- severity: no problem(不輸出到 JSON)。\n\n另一範例:\nfunc connectDB() { db, _ = sql.Open(\"mysql\", \"user:pass@tcp(host)/db\") } // 硬編碼密碼。\n- 分析:邏輯正確,但安全問題(硬編碼憑證);其他面向 OK。\n- severity: high(高風險,因為安全漏洞)。\n- issues_found: [\"硬編碼憑證\"]\n- comment: \"檔案中存在硬編碼資料庫憑證的安全漏洞,建議使用環境變數或秘密管理工具。\"\n\n以下是程式碼內容:\n\n{{ $json[\"changes_with_first_line\"].toJsonString() }}\n\n---\n\n你的回覆必須嚴格遵守以下結構,且不得包含標題與 JSON 塊以外的任何文字:\n1. 先輸出標題:GitLab Discussion Request\n2. 然後立即輸出以 ```json\n\nJSON 格式如下(抓取元 input json 資料不要異動):\n\n每個檔案最多一個討論\n\n討論行對應該檔案的第一個變更行\n\ncomment 必須包含該檔案所有重要問題的綜合評論,使用繁體中文\n\nissues_found 為陣列,每項為短字串描述主要問題,不可用段落文字\n\nseverity 分為四個等級:high(高風險)、medium(中風險)、low(低風險)、no problem(無問題)。請分析所有檔案,但只在 JSON 回應中輸出 severity 為 high、medium、low 的檔案。\n如果檔案評估為 no problem,則不要在 gitlab_discussions 陣列中包含該檔案。\n\n回應的 position 拿取原本的資料 {{ $json[\"changes_with_first_line\"].toJsonString() }}\n\n```json\n{\n  \"gitlab_discussions\": [\n    {\n      \"file_path\": \"<檔案完整路徑>\",\n      \"comment\": \"<針對整個檔案的重要問題綜合評論,繁體中文>\",\n      \"severity\": \"high|medium|low|no problem\",\n      \"issues_found\": [\"短字串描述主要問題\"],\n      \"position\": {\n        \"base_sha\": \"<base sha>\",\n        \"head_sha\": \"<head commit sha>\",\n        \"start_sha\": \"<start commit sha>\",\n        \"position_type\": \"text\",\n        \"new_path\": \"<檔案完整路徑>\",\n        \"old_path\": \"<檔案完整路徑>\",\n        \"new_line\": null or 數字(依照 input),\n        \"old_line\": null or 數字(依照 input)\n      }\n    }\n  ]\n}",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.2,
      "position": [
        1584,
        592
      ],
      "id": "d7a3205d-fb14-4b89-897c-a95bd4e626c2",
      "name": "AI Agent1",
      "notes": "const PROMPT = `\n請你扮演一位資深 Golang 工程師,針對以下 Go 程式碼進行全面性的 Code Review。請依照以下面向給出具體建議:\n\n1. ✅ 程式邏輯是否正確、是否有潛在錯誤或邊界條件未處理\n2. 🧹 是否符合 Go 的慣用寫法(idiomatic Go),例如錯誤處理、命名風格、簡潔性\n3. 🔒 是否有安全性問題,例如硬編碼、未處理的錯誤、資源洩漏等\n4. 🚀 是否有效能瓶頸,例如不必要的記憶體分配、重複運算、goroutine 使用不當\n5. 📚 是否具備良好的可讀性與可維護性,包括註解、結構清晰度、模組化程度\n\n請以條列方式回覆,每一點請包含:\n- 問題描述\n- 改善建議\n- 若有需要,請附上修改後的程式碼片段\n\n以下是程式碼內容:\n\\`\\`\\`go\n{{ $json[\"mergedDiffs\"] }}\n\\`\\`\\`\n\n---\n\n### 📝 GitLab Discussion Request\n請在回覆最後,**務必提供一份合法 JSON 區塊**,格式如下(每個檔案最多一個討論):\n\n\\`\\`\\`json\n{\n  \"gitlab_discussions\": [\n    {\n      \"file_path\": \"完整檔案路徑\",\n      \"comment\": \"針對整個檔案的重要評論(繁體中文,綜合性總結)\",\n      \"severity\": \"high|medium|low\",\n      \"issues_found\": [\"短字串問題1\", \"短字串問題2\"]\n    }\n  ]\n}\n\\`\\`\\`\n\n**重要規則:**\n1. 請務必輸出合法 JSON,且只出現一次 JSON 區塊\n2. 每個檔案最多一個討論,在該檔案的第一個異動行留言\n3. comment 要包含該檔案所有重要問題的綜合評論\n4. 只針對 severity 為 \"high\" 或 \"medium\" 的檔案提供此資料\n5. issues_found 請用短字串(避免段落文字)\n`;\n"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "gitlab-ai-code-review",
        "contextWindowLength": 50
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1680,
        976
      ],
      "id": "e300a7fb-4ee6-4dba-829a-96304088c119",
      "name": "Simple Memory1"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        1584,
        832
      ],
      "id": "1d6b0f80-5ae4-4571-81a7-3daa78a97115",
      "name": "Google Gemini Chat Model1",
      "credentials": {
        "googlePalmApi": {
          "id": "gemini key",
          "name": "Google Gemini(PaLM) Api account"
        }
      }
    },
    {
      "parameters": {
        "content": "(線上 prod)\n只針對異常的檔案留言給建議\n"
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -432,
        624
      ],
      "typeVersion": 1,
      "id": "f92b3c9b-1ee3-4dfe-a435-1fcfe28ea0fc",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://{your gitlab host}/api/v4/projects/{{ $node[\"get diff\"].json[\"project_id\"] }}/merge_requests/{{ $node[\"get diff\"].json[\"iid\"] }}/notes",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "PRIVATE-TOKEN",
              "value": "={{ $('gitlab-project token list').item.json.token }}"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "body",
              "value": "=沒有異常內容, {{ $json.summary }}"
            }
          ]
        },
        "options": {
          "redirect": {
            "redirect": {}
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2432,
        768
      ],
      "id": "3e777f57-0e81-44e0-8c85-8bd2dbc2a3d8",
      "name": "note summary"
    },
    {
      "parameters": {
        "jsCode": "const changes = $json.changes || [];\nconst headSha = $json?.diff_refs?.head_sha || '';\nconst baseSha = $json?.diff_refs?.base_sha || '';\nconst startSha = $json?.diff_refs?.start_sha || baseSha;\n\nconst projectId = $json.project_id || 0;\nconst iid = $json.iid || 0;\n\n// 只解析每個 diff 的第一個異動行\nfunction getFirstChangedLine(diff) {\n  const lines = diff.split('\\n');\n  let oldLineNum = 0;\n  let newLineNum = 0;\n  let headerFound = false;\n\n  for (const line of lines) {\n    if (line.startsWith('@@')) {\n      const match = line.match(/@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n      if (match) {\n        oldLineNum = parseInt(match[1]) - 1;\n        newLineNum = parseInt(match[2]) - 1;\n        headerFound = true;\n      }\n    } else if (headerFound && line.startsWith('+') && !line.startsWith('+++')) {\n      newLineNum++;\n      return {\n        type: 'added',\n        old_line: null,\n        new_line: newLineNum,\n        content: line.substring(1),\n        original_line: line\n      };\n    } else if (headerFound && line.startsWith('-') && !line.startsWith('---')) {\n      oldLineNum++;\n      return {\n        type: 'removed',\n        old_line: oldLineNum,\n        new_line: null,\n        content: line.substring(1),\n        original_line: line\n      };\n    } else if (headerFound && line.startsWith(' ')) {\n      oldLineNum++;\n      newLineNum++;\n    }\n  }\n\n  return null;\n}\n\n// 為每個檔案生成第一個變更行資料\nconst changesWithFirstLine = changes.map(change => {\n  const firstChangedLine = getFirstChangedLine(change.diff);\n  \n  if (!firstChangedLine) {\n    return null;\n  }\n\n  return {\n    diff: change.diff,\n    new_path: change.new_path,\n    old_path: change.old_path || change.new_path, // 確保 old_path 不為空\n    new_file: change.new_file,\n    deleted_file: change.deleted_file,\n    old_line: firstChangedLine.old_line,\n    new_line: firstChangedLine.new_line,\n    baseSha: baseSha,\n    headSha: headSha,\n    startSha: startSha\n  };\n}).filter(item => item !== null); // 過濾掉無效項目\n\n// 輸出 JSON,欄位符合 GitLab Discussion API\nreturn [\n  {\n    json: {\n      project_id: projectId,\n      iid,\n      changes_with_first_line: changesWithFirstLine\n    }\n  }\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1296,
        592
      ],
      "id": "31be98c8-a92d-48a9-b110-f6834483994a",
      "name": "get diff"
    },
    {
      "parameters": {
        "jsCode": "// ===== 只抓 GitLab Discussion Request =====\nfunction extractGitlabDiscussion(text) {\n  if (!text) return null;\n\n  // 找到 GitLab Discussion Request 標題\n  const marker = 'GitLab Discussion Request';\n  const markerIndex = text.indexOf(marker);\n  if (markerIndex === -1) return null;\n\n  // 從 marker 開始找 ```json ... ```\n  const jsonStart = text.indexOf('```json', markerIndex);\n  const jsonEnd = text.indexOf('```', jsonStart + 1);\n  if (jsonStart === -1 || jsonEnd === -1) return null;\n\n  // 擷取區塊,去掉 ```json\n  const jsonStr = text.slice(jsonStart + 7, jsonEnd).trim();\n\n  try {\n    return JSON.parse(jsonStr);\n  } catch (err) {\n    console.error('GitLab Discussion JSON 解析失敗:', err);\n    return null;\n  }\n}\n\ntry {\n  // 取得 AI 回應字串\n  const aiResponse = $json.output \n    || $json.response \n    || $input.all()[0]?.json?.output \n    || $input.all()[0]?.json?.response \n    || '';\n\n  if (!aiResponse) {\n    return [{\n      json: {\n        summary: '錯誤:未收到 AI 回應',\n        discussions: []\n      }\n    }];\n  }\n\n  // 解析 GitLab Discussion JSON\n  const parsedData = extractGitlabDiscussion(aiResponse);\n\n  if (parsedData && parsedData.gitlab_discussions?.length > 0) {\n    return [{\n      json: {\n        summary: `找到 ${parsedData.gitlab_discussions.length} 個討論項目`,\n        discussions: parsedData.gitlab_discussions\n      }\n    }];\n  }\n\n  return [{\n    json: {\n      summary: 'AI 回應中沒有需要討論的異常檔案',\n      discussions: []\n    }\n  }];\n} catch (error) {\n  return [{\n    json: {\n      summary: `執行失敗:${error.message}`,\n      discussions: []\n    }\n  }];\n}\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1936,
        592
      ],
      "id": "3ecea285-252d-46eb-a9da-204015d2662f",
      "name": "generate discussion data "
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "7875626d-0b2d-4bc8-a473-779854b05c03",
              "leftValue": "={{ $json.discussions }}",
              "rightValue": "[",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2144,
        592
      ],
      "id": "3393a3b1-97a8-452e-80ce-6c49d30deb8b",
      "name": "if has discussion"
    },
    {
      "parameters": {
        "jsCode": "const projectId = $node[\"get diff\"].json[\"project_id\"];\nconst iid = $node[\"get diff\"].json[\"iid\"];\n// 回傳格式與原始 JSON 類似\nlet result = $input.first().json.discussions.map((discussion) => {\n  return {\n    ...discussion,\n    projectId,\n    iid\n  }\n})\nreturn result;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2432,
        576
      ],
      "id": "3679d630-7561-4b0a-bd19-dde02934b71e",
      "name": "to items"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://{{your gitlab host}}.vip/api/v4/projects/{{ $json.projectId }}/merge_requests/{{ $json.iid}}/discussions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "=PRIVATE-TOKEN",
              "value": "={{ $('gitlab-project token list').item.json.token }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "body",
              "value": "=- 嚴重程度: {{ $json.severity }}\n- 異常摘要:{{ $json.issues_found }}\n- 評論: {{ $json.comment }}"
            },
            {
              "name": "position[position_type]",
              "value": "text"
            },
            {
              "name": "position[old_path]",
              "value": "={{ $json.position.old_path }}"
            },
            {
              "name": "position[new_path]",
              "value": "={{ $json.position.new_path }}"
            },
            {
              "name": "position[start_sha]",
              "value": "={{ $json.position.start_sha }}"
            },
            {
              "name": "position[head_sha]",
              "value": "={{ $json.position.head_sha }}"
            },
            {
              "name": "position[base_sha]",
              "value": "={{ $json.position.base_sha }}"
            },
            {
              "name": "=position[new_line]",
              "value": "={{ $json.position.new_line !== null ? $json.position.new_line : '' }}"
            },
            {
              "parameterType": "=formData",
              "name": "=position[old_line]",
              "value": "={{ $json.position.old_line !== null ? $json.position.old_line : '' }}"
            }
          ]
        },
        "options": {
          "redirect": {
            "redirect": {}
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2768,
        576
      ],
      "id": "9c981c2a-a937-4da0-99de-ddb0eb434b74",
      "name": "discuss(comment) each file"
    },
    {
      "parameters": {
        "jsCode": "// 專案 token 設定: 格式 key:id, value:token\n// 目前多 token(credential) 整合到同一個 workflow 沒有支援,所以使用程式做整併\n// 參考網址: https://community.n8n.io/t/use-the-same-workflow-with-different-credentials/56940\nvar mapToken = new Map();\nmapToken.set(\"{project_id}\", \"glpat-XXXXX\") // callduck\nmapToken.set(\"{project_id}\", \"glpat-XXXXX\") // beacon\n\nreturn $input.all().map(item => {\n  const projectId = item.json.body.project.id;\n  const iid = item.json.body.object_attributes.iid\n  const token = mapToken.get(projectId.toString());\n  const sourceBranch = item.json.body.object_attributes.source_branch\n  return {\n    json: {\n      projectId: projectId,\n      iid: iid,\n      token: token,\n      sourceBranch: sourceBranch\n    }\n  }\n})"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        688,
        592
      ],
      "id": "8b3955bf-ef9f-4751-bacd-41b41a3fa900",
      "name": "gitlab-project token list"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "{generated_uuid}",
        "options": {}
      },
      "id": "80014652-48ee-4719-89a4-71e5923c0a70",
      "name": "GitLab Webhook(Beacon)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        48,
        848
      ],
      "webhookDescription": {
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "responseData": "default"
      },
      "webhookId": "cd96d9d1-1bf7-4781-958e-3bdf590f36fa"
    },
    {
      "parameters": {
        "url": "=https:/{yourgitlab host}/api/v4/projects/{{ $json[\"projectId\"] }}/merge_requests/{{ $json[\"iid\"] }}/changes",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gitlabApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "PRIVATE-TOKEN",
              "value": "={{ $json.token }}"
            }
          ]
        },
        "options": {
          "redirect": {
            "redirect": {}
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        992,
        592
      ],
      "id": "c4412d8f-1f25-4ef2-a9c0-445ccefd7895",
      "name": "diff",
      "credentials": {
        "gitlabApi": {
          "id": "crzxH5zCI2MdAxsv",
          "name": "GitLab account"
        }
      }
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "d73bba06-4622-41d9-acb7-4c94412ac5af",
        "options": {}
      },
      "id": "89e368fc-0928-498d-b1de-6dca15851260",
      "name": "GitLab Webhook(Nami)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        48,
        608
      ],
      "webhookDescription": {
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "responseData": "default"
      },
      "webhookId": "d73bba06-4622-41d9-acb7-4c94412ac5af",
      "disabled": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "650f84b9-7505-47b5-add0-7a3354592b08",
              "leftValue": "={{ \n  (\n    $json.body.object_attributes.action === \"open\" ||\n    $json.body.object_attributes.action === \"update\" \n  ) && \n  $json.body.object_attributes.state === \"opened\" \n}}",
              "rightValue": "\"update\"",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        368,
        608
      ],
      "id": "5c83a22a-8df6-49ac-9aca-094656a27618",
      "name": "need code review or not"
    }
  ],
  "pinData": {},
  "connections": {
    "AI Agent1": {
      "main": [
        [
          {
            "node": "generate discussion data ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory1": {
      "ai_memory": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "get diff": {
      "main": [
        [
          {
            "node": "AI Agent1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "generate discussion data ": {
      "main": [
        [
          {
            "node": "if has discussion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "if has discussion": {
      "main": [
        [
          {
            "node": "to items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "note summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "to items": {
      "main": [
        [
          {
            "node": "discuss(comment) each file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "gitlab-project token list": {
      "main": [
        [
          {
            "node": "diff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitLab Webhook(Beacon)": {
      "main": [
        [
          {
            "node": "need code review or not",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "diff": {
      "main": [
        [
          {
            "node": "get diff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitLab Webhook(Nami)": {
      "main": [
        [
          {
            "node": "need code review or not",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "need code review or not": {
      "main": [
        [
          {
            "node": "gitlab-project token list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveExecutionProgress": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "versionId": "30aa51fb-05c5-413c-b85b-8e01995791ac",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "d1c1e5a8f255713e433449e47160fa73b6ec995c5402084cb4d58e55b2e5a66c"
  },
  "id": "yLjkgrtCIGEVjllZ",
  "tags": []
}

匯入後,畫面上可能會看到幾個節點出現驚嘆號,需要調整以下設定:

  • GitLab Webhook:移除 {generated_uuid},讓 n8n 自動產出 UUID。

  • HTTP Request 節點(diff + discuss(comment each file)):調整 GitLab Host 與 Access Token。

  • gitlab-project-token list:填入你的 Access Token。

  • AI Agent:選擇你要的模型與 API Key。

總之,所有金鑰與 token 等敏感資訊都需要自行填寫。完成後即可直接使用,是不是超級方便!

https://ithelp.ithome.com.tw/upload/images/20250925/20121499v6FjBMizQR.png

小結

今天分享了如何透過 n8n 匯出與匯入模板,也公開了我做好的 AI Code Review workflow。
若在使用上有任何問題或心得,歡迎隨時回饋給我,我會非常樂意與大家交流。


上一篇
[Day 25] AI 驅動的 Code Review:如何導入團隊流程?
下一篇
[Day 27] 三大 AI 模型官方文件的 Prompt 最佳實踐
系列文
AI 驅動的 Code Review:MCP 與 n8n 自動化實踐30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言