← 回課程首頁
Claude → ChatGPT 快速轉換課程

Part 6
案件追蹤系統完整實戰

以 ASP.NET Core Web API + PostgreSQL 完整走過需求、規格、架構、資料模型、API、實作、測試與交付。ChatGPT 負責加速分析與產出,人負責需求權限、證據與最終核准。

01|案例與完成範圍

案件追蹤系統

角色:承辦人、主管、系統管理員。核心功能:建立案件、指派承辦人、合法狀態轉換、處理紀錄、查詢、稽核。

本次垂直切片完成條件
變更案件狀態權限、狀態機、並行控制、稽核、API 錯誤、整合測試完整
不納入附件儲存、通知、全文檢索與完整前端;列入 backlog,不假裝已完成
為什麼選垂直切片:一次驗證需求到資料庫的完整鏈,比先建立所有空殼 controller、service、repository 更能暴露真實風險。

02|把 ChatGPT 放進 SDLC,而不是取代 SDLC

1 需求

找缺口、矛盾、角色、狀態、驗收。輸出「需求/假設/待確認」表。

2 設計

狀態機、API contract、schema、constraint、並行與稽核策略。

3 實作

先最小垂直切片;每次變更都能編譯、測試與 review。

4 驗證

單元、整合、權限、並行、migration 與失敗情境。

5 交付

OpenAPI、ADR、migration、測試證據、操作與回復說明。

6 回饋

把缺陷回寫需求與測試;不讓文件、schema 與 code 漂移。

第一輪 Prompt:
先不要寫程式。針對案件狀態變更,找出會改變
權限、狀態機、資料一致性、稽核與驗收測試的未知事項。
依風險排序問 7 題;每題說明若不確認的後果。

03|從需求建立可驗證設計

狀態轉換

目前可轉換角色必要條件
DraftSubmitted承辦人必要欄位完整
SubmittedInReview主管已指派審查人
InReviewApproved / Rejected主管意見不可空白
RejectedDraft承辦人建立修正紀錄

資料庫先守住不變條件

create table cases (
  id uuid primary key,
  title varchar(200) not null,
  status varchar(30) not null
    check (status in ('Draft','Submitted','InReview','Approved','Rejected')),
  version integer not null default 1,
  updated_at timestamptz not null
);

create table case_status_history (
  id uuid primary key,
  case_id uuid not null references cases(id),
  from_status varchar(30) not null,
  to_status varchar(30) not null,
  changed_by uuid not null,
  reason text,
  changed_at timestamptz not null
);
分工:應用層負責角色、轉換規則與友善錯誤;資料庫 constraint 負責最後防線。不能因為已有 C# validation 就放棄資料庫完整性。

04|API 與核心實作(閱讀示範)

本節是閱讀示範:用來看懂設計意圖,不是可直接部署的實作。可執行的部分放在 §06。

項目Contract
EndpointPOST /api/cases/{id}/status-transitions
輸入targetStatus、reason、expectedVersion
成功200,回傳新狀態、版本、updatedAt
錯誤400 格式/規則、403 權限、404 不存在、409 並行衝突
public sealed record ChangeStatusRequest(
    CaseStatus TargetStatus,
    string? Reason,
    int ExpectedVersion);

public Result ChangeStatus(
    CaseStatus target, Guid actorId, string? reason, int expectedVersion)
{
    if (Version != expectedVersion)
        return Result.Conflict("CASE_VERSION_CONFLICT");

    if (!StatusTransitions.IsAllowed(Status, target))
        return Result.Invalid("INVALID_STATUS_TRANSITION");

    var previous = Status;
    Status = target;
    Version++;
    UpdatedAt = DateTimeOffset.UtcNow;
    _history.Add(CaseStatusChanged.Create(
        Id, previous, target, actorId, reason, UpdatedAt));

    return Result.Success();
}
範例刻意未宣稱完整:仍需 authorization policy、transaction、EF mapping、unique/check constraint、錯誤映射、logging 與實際併發測試。成熟做法是要求 ChatGPT 明列未實作部分,而不是把片段稱為 production-ready。

上面那段版本檢查,擋不住兩個服務實例

if (Version != expectedVersion)記憶體內的比對。它能擋掉「客戶端拿著舊版本送出」,但擋不住「兩個請求各自讀到同一筆舊資料、各自比對通過」。下面這個時序,兩邊都會通過檢查:

時點請求 A請求 B學習重點
1讀取版本 7讀取版本 7兩個應用程式中的物件各自持有相同舊值。
2expectedVersion = 7,比對通過expectedVersion = 7,比對通過記憶體比對不能阻止另一個請求也通過。
3嘗試寫入版本 8嘗試寫入版本 8會不會衝突,要看資料庫寫入條件與影響列數。
4若更新沒有版本條件,兩次寫入可能都被接受。前置檢查與原子更新不是同一件事。

版本必須參與寫入條件

真正的關鍵機制,是把版本放進資料庫更新的 WHERE 條件,再用影響列數判斷有沒有成功。EF Core 的 optimistic concurrency 就是這個做法:把 concurrency token 放進更新條件,找不到符合舊 token 的列時,回報並行衝突。

-- 概念 SQL;不是可直接部署的完整 repository 實作。
-- 角色、案件可見範圍、合法轉換與理由必填,仍須先驗證。
UPDATE cases
SET status = @target_status,
    version = version + 1,
    updated_at = @now
WHERE id = @id
  AND version = @expected_version
  AND status = @expected_status
RETURNING id, status, version;

-- 0 列:這次狀態轉換沒有完成;依 contract 處理衝突或不存在。
-- 1 列:才可以在同一個交易內寫入對應的 history。
-- 任一步驟失敗,整個交易必須回滾。
三個容易踩到的地方:第一,EF Core 文件裡的 rowversion 範例是 SQL Server 的型別,不要直接當成 PostgreSQL 的欄位型別來用。第二,EF Core 單次 SaveChanges 在支援交易的 provider 下具備原子性;一旦你拆成多次儲存或混用原始 SQL,就必須自己確認它們用的是同一個交易與連線。第三,這段是設計方向,不代表任何特定程式已經正確實作——要用 §06 的雙請求測試證明。

05|測試不是只有 happy path

單元測試

  • 合法與非法狀態轉換
  • 理由必填規則
  • 版本衝突
  • 歷程事件內容

整合測試

  • 實際 PostgreSQL constraint
  • transaction 回滾
  • 兩個請求同時更新
  • HTTP 400/403/404/409 contract

安全測試

  • 未登入與錯誤角色
  • 跨單位案件越權
  • 稽核紀錄不可竄改
  • 輸入長度與惡意內容

Migration 測試

  • 既有資料能否轉換
  • upgrade/rollback 路徑
  • 鎖表與停機風險
  • 備份與復原演練

06|畢業實作與驗收(可執行實驗)

這裡才是要動手跑的部分。請把它和 §04 的閱讀示範分開看:示範讓你看懂,實驗才產生證據。

  1. 寫 10 條可驗收需求,標示來源與待確認。
  2. 完成狀態轉換表、API contract、schema 與 ADR。
  3. 建立可啟動的 ASP.NET Core 專案與 PostgreSQL migration。
  4. 完成一條狀態變更垂直切片。
  5. 至少 12 個測試:規則、權限、並行、資料庫與 HTTP。
  6. 執行 build、test、migration 驗證,保存輸出。
  7. 產生 OpenAPI、README、部署與回復說明。
  8. 讓 ChatGPT 以 hostile reviewer 反查,再由你判斷是否接受。

真正完成

不是「ChatGPT 已產生 code」,而是新環境能按 README 啟動;測試可重跑;資料庫規則存在;每項需求能追到設計與測試;未知與風險未被隱藏。

驗收契約:要觀察到什麼,什麼不算數

驗收項目應觀察到的結果不足的替代品
相同舊版本,兩個獨立請求同時轉換只有一筆成功轉換,另一筆依 contract 回報衝突;歷程筆數與成功次數相符。對同一個物件連續呼叫兩次。
寫入歷程時故意觸發失敗主狀態與版本都回復到交易前。只 assert 回傳了錯誤訊息。
錯誤角色與跨單位存取拒絕未授權操作,且沒有任何資料變更。只在畫面上隱藏按鈕。
空白理由與非法狀態轉換拒絕不符規則的輸入,並有對應測試。只測成功路徑。
新環境依 README 啟動可重跑 migration 與整合測試,附執行命令、版本與結果。AI 生成的「預期會通過」文字。
目前的落差要講清楚:本 repo 只有教材 HTML,尚未附上可啟動的 ASP.NET Core 專案、migration 專案或後端測試專案。也就是說,上表是你完成畢業實作時的驗收契約,不是課程已經幫你跑過的結果。實作時請自己建立獨立分支的 starter,鎖定依賴版本,並保留啟動步驟與失敗案例。程式審查能幫你找缺陷,但不能取代實際執行證據。

07|20 題綜合情境測驗

總分至少 85,且四個能力軸均達 70% 才通過。

這個分數代表什麼:這是辨識題成績。它不能單獨代表你完成了 Part 6——畢業實作與 §06 的驗收契約才是應用證據。請把兩個成績分開呈現,不要合成一個看起來精準的熟練度數字。

官方技術參考

本章維護紀錄

項目內容
查核日期/依據2026-09-13;依 2026-09 現況查核報告(內部紀錄,未發布於網站) 的 F02、F03、F12 修訂。
主張與來源並行控制:EF Core — Handling Concurrency Conflicts;交易原子性:EF Core — Using Transactions;子集合唯一性:PostgreSQL — Partial Indexes
已執行的步驟已查閱官方文件並改寫正文;在本 repo 建立可啟動專案,執行任何資料庫或併發測試。
待確認尚不存在於本 repo 的 .NET/PostgreSQL 實驗專案(starter、migration、整合測試)。