實戰背景
在現代的分散式或多模組 Web 系統中,後端 Web API 控制器(Controllers)定義的路由端點(Route Endpoints)常被前端 Single Page App(SPA)、背景排程 Worker 或其他微服務直接調用。
痛點與問題描述:
我們需要重構後端核心的支付介面 POST /api/v1/payments/charge,將其重命名為 POST /api/v2/payments/execute 並修改其請求 Body 格式(移除了 zipCode,改為必填的 billingAddress)。
這項改動一旦發佈,如果前端程式碼或背景排程 Worker 依然使用舊的路徑與參數發送請求,將會引發大面積的生產環境 400 Bad Request 或是 404 Not Found 報錯。傳統的文字搜尋很難穿透跨專案的物理邊界,因此,我們需要利用 Codebase Memory 知識圖譜 的跨服務關聯分析,進行全域的安全變更。
規格定義
本次變更計畫的 ID 為 api-route-refactor。我們在 OpenSpec 增量規格中定義了以下驗收標準:
# 實戰範例 001:openspec/changes/api-route-refactor/specs/api-endpoint/spec.md
## ADDED/MODIFIED Requirements
### Requirement: Upgraded payments execution endpoint
系統必須(SHALL)將原有的 `/api/v1/payments/charge` 路由安全遷移至 `/api/v2/payments/execute`。
- 新的 API 控制器必須(SHALL)接收 `PaymentRequestV2` 參數,且包含必填的 `billingAddress` 欄位。
#### Scenario: Verify cross-service endpoint migration
- **WHEN** 執行全域集成測試並模擬前端請求
- **THEN** 所有的前端呼叫點與背景排程 Worker 必須(SHALL)無錯地改呼叫 `/api/v2/payments/execute` 且通過校驗,原有舊端點必須(SHALL)返回 `301 Moved Permanently` 或是安全廢棄。
端到端 8 輪 AI 協作紀錄
以下是 You (開發者) 與 AI 助手在實施此重構時的完整對話歷史。
── 第 1 輪:定位 API 控制器與路徑定義 ──
- You:
/opsx:explore "我需要重構支付 API 路由。請幫我用 search_graph 搜尋定義了 payments/charge 路由的類別與位置。" - AI:
好的,已進入探索模式。我將呼叫 codebase-memory 的
search_graph工具,尋找與 “payments” 或 “charge” 相關的控制器 Class 節點。CM 呼叫:
search_graph(query: "PaymentController", type: "class")
CM 回傳:[ { "qualifiedName": "ControlSystem.API.Controllers.PaymentController", "type": "class", "filePath": "/src/API/Controllers/PaymentController.cs", "methodsCount": 3 } ]分析:
找到了位於/src/API/Controllers/PaymentController.cs中的PaymentController類別。接下來,我將檢索該類別內部的方法與定義,以確認具體的 Action 路由。
── 第 2 輪:讀取控制器 Action 代碼 ──
- You:
請使用 get_code_snippet 讀取 PaymentController 中 charge Action 的 Verbatim 原始碼。 - AI:
我將呼叫
get_code_snippet讀取該類別中對應的 Action 實作。CM 呼叫:
get_code_snippet(qualifiedName: "ControlSystem.API.Controllers.PaymentController.Charge")
CM 回傳:- 檔案路徑:
/src/API/Controllers/PaymentController.cs(第 18 行到第 30 行) - 原始碼:
[HttpPost("v1/charge")] public async Task<IActionResult> Charge([FromBody] ChargeRequest request) { var result = await _paymentService.Process(request.Amount, request.ZipCode); return Ok(result); }
分析:
控制器的方法被定義為HttpPost("v1/charge"),接受ChargeRequest物件(內部含有ZipCode)。 接下來,我們需要找出整個方案中,有哪些前端組件、背景 Worker 或者是單元測試引用了這個 Endpoint。 - 檔案路徑:
── 第 3 輪:跨服務依賴檢索 ──
- You:
使用 trace_path 追蹤對 Charge 方法的入站依賴,確認有哪些外部呼叫端。 - AI:
好的,我將呼叫
trace_path工具,沿著圖譜的關係鏈追蹤所有引用了PaymentController.Charge的位置。CM 呼叫:
trace_path(symbolName: "ControlSystem.API.Controllers.PaymentController.Charge", direction: "inbound")
CM 回傳:[ { "caller": "ControlSystem.API.Controllers.PaymentController.Charge", "calledBy": [ { "caller": "ControlSystem.Workers.SubscriptionBillingJob.Execute", "location": "/src/Workers/SubscriptionBillingJob.cs#L72", "type": "BackendWorkerCall" }, { "caller": "src/frontend/api/payments.ts", "location": "/src/frontend/api/payments.ts#L12", "type": "HTTPRouteDependency" } ] } ]分析:
圖譜精確抓取到了跨專案的兩個重要呼叫源:- 後端背景 Worker:
SubscriptionBillingJob.cs的第 72 行調用了此 API。 - 前端 TypeScript API 封裝:
src/frontend/api/payments.ts的第 12 行向此路由發送了請求。
我們必須同步修改這兩個地方。我現在退出探索模式,並為您建立變更計畫。
- 後端背景 Worker:
── 第 4 輪:建立提案 ──
- You:
/opsx:propose "將支付 API 升級至 v2,重構控制器、背景 Worker 與前端呼叫點,補齊 billingAddress 參數並移除 zipCode" - AI:
好的,提案
api-route-refactor已建立!- proposal.md: 說明升級支付 API 的必要性與防禦性修改前端/背景 Worker 的步驟。
- specs/api-endpoint/spec.md: 定義 v2 API 的驗收 Scenario。
- tasks.md: 任務拆解如下:
- 1.1 修改
PaymentController.cs路由為v2/execute並改用PaymentRequestV2。 - 1.2 修改背景 Worker
SubscriptionBillingJob.cs。 - 1.3 修改前端
payments.ts。 - 1.4 執行建置與測試。
- 1.1 修改
請輸入
/opsx:apply來開始實作。
── 第 5 輪:重構控制器 (Task 1.1) ──
- You:
/opsx:apply - AI:
Implementing: api-route-refactor (schema: spec-driven)
正在處理任務 1.1:重構後端控制器與 Data Model。
我已在
src/API/Controllers/PaymentController.cs中建立了全新的 V2 Action,並廢棄了 V1(改為回傳 301):[HttpPost("v2/execute")] public async Task<IActionResult> Execute([FromBody] PaymentRequestV2 request) { var result = await _paymentService.ProcessV2(request.Amount, request.BillingAddress); return Ok(result); }✓ 任務 1.1 完成!已更新
tasks.md狀態。
── 第 6 輪:重構背景 Worker (Task 1.2) ──
- You:
繼續任務 1.2,重構 SubscriptionBillingJob.cs。 - AI:
Implementing: api-route-refactor
正在處理任務 1.2:修改背景 Worker 調用。
我已修改了
src/Workers/SubscriptionBillingJob.cs的第 72 行,將原有的 JSON 請求體修改為傳送BillingAddress:var payload = new { amount = subscription.Price, billingAddress = subscription.UserAddress // 替代了舊的 zipCode }; var response = await _client.PostAsJsonAsync("api/v2/payments/execute", payload);✓ 任務 1.2 實作完成。
── 第 7 輪:重構前端 API 封裝與驗證 ──
- You:
繼續實作任務 1.3 前端 API 修改,並執行建置驗證。 - AI:
Implementing: api-route-refactor
正在處理任務 1.3 & 1.4:前端 API 修改與建置驗證。
- 前端修改:已在
src/frontend/api/payments.ts中將請求網址更改為/api/v2/payments/execute並更新 TypeScript 介面定義。 - 編譯建置:執行
pnpm run build通過。後端編譯dotnet build成功。 - 集成測試:模擬發送支付請求,後端順利解析
billingAddress且成功完成刷卡,返回transactionId。
已將變更同步至主規格並歸檔計畫。
Archive Complete
變更計畫已成功歸檔!
- 前端修改:已在
── 第 8 輪:重構效益總結 ──
- You:
總結本次利用圖譜進行 API 重構的價值。 - AI:
本次重構中,
codebase-memory顯示出其無與倫比的跨服務關聯價值:- 消除盲區:前端 TypeScript 檔案與後端 C# 檔案屬於完全不同的專案目錄。傳統的 C# IDE 只能追蹤 C# 內部的呼叫,對前端
payments.ts中的字串調用完全是瞎子。圖譜成功把「HTTP 路由」作為邊關係串聯起來。 - 零漏網之魚:背景 Worker
SubscriptionBillingJob如果這次沒有被同步修改,會導致排程扣款在深夜執行時全部爆錯。圖譜精確定位了它,防範了潛在的線上大事故。
- 消除盲區:前端 TypeScript 檔案與後端 C# 檔案屬於完全不同的專案目錄。傳統的 C# IDE 只能追蹤 C# 內部的呼叫,對前端