Project

General

Profile

CR #201 » CSP-003_錯誤處理與回應碼規範.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

CSP-003 錯誤處理與回應碼規範(Error Handling & Response Code)

項目 內容
文件編號 CSP-003
版本 1.3
狀態 已核定
撰寫者 SA / Tech Lead
審查者 Security Architect
對應需求 NFR-REL 可靠性群組;BRD-001 §6 例外與補償

1. 目的

統一全系統之錯誤分類、錯誤碼、HTTP 狀態對應、回應封套與例外處理策略,
確保:①前端可據錯誤碼精準處理;②不洩漏內部實作;③可觀測、可追蹤。


2. 錯誤碼分類

錯誤碼格式:CC-<SVC>-<NNNN>,其中 <SVC> 為 2–4 字母服務代碼(依服務別長度不一:如 AUTH=4、LIM=3、GW=2),與 SDD-006 §3.2 登錄表、OpenAPI Error.code 之 ^CC-[A-Z]{2,4}-[0-9]{4}$ 完全一致。

唯一登錄表:所有可用錯誤碼以 SDD-006 §3.4 為準(共 50+ 碼)。Coder 不得自創錯誤碼;新增碼須先擴充 SDD-006 §3.4 並同步 gen_openapi.py 之 Error.code.pattern。

| 類別位(首位) | 意義 | HTTP | 可否重試 | 代表碼 |
|---:|---|---|:--:|:--:|---|
| 1 | 業務規則拒絕 | 422 | ❌ | CC-AUTH-1001、CC-LIM-1001、CC-POST-1001 |
| 2 | 併發/冪等衝突 | 409 | 部分可 | CC-POST-2001、CC-POST-2002 |
| 3 | 狀態機衝突 | 409 | ❌ | CC-CARD-3001、CC-POST-3001 |
| 4 | 請求與資源 | 400 / 404 / 405 / 409 | ❌ | CC-PLT-4001、CC-PLT-4002、CC-PLT-4003、CC-PLT-4040、CC-PLT-4050、CC-POST-4001 |
| 5 | 外部與基礎設施 | 503 / 500 | ✅(冪等者) | CC-PLT-5000、CC-PLT-5001、CC-PLT-5002、CC-AUTH-5001 |

v1.2 更正:原表類別位 4 之代表碼誤引 CC-AUTH-4004——該碼不存在於
SDD-006 §3.4 登錄表(登錄表目前尚無任何 404 碼)。依本文件 §2「唯一登錄表」原則,
更正為已登錄之 CC-POST-4001/CC-POST-4002。
若日後確有「資源不存在」情境,應先擴充 SDD-006 §3.4 再使用,不得直接引用未登錄碼。

2.1 類別位 4「請求與資源」之語意(v1.3 擴充,CR-004)

類別位 4 =「請求本身或其所指資源無法被受理」,涵蓋四個子語意,
皆屬請求層,尚未進入業務邏輯:

子語意 HTTP 何時使用 代表碼
請求不合法 400 必填標頭/查詢參數缺失、主體格式錯誤、參數型別或 Bean Validation 失敗 CC-PLT-4001/CC-PLT-4002/CC-PLT-4003
資源或路徑不存在 404 無對應路由、聚合根查無 CC-PLT-4040、CC-PTY-4040、CC-POST-4040
方法不支援 405 HTTP 動詞與端點不符 CC-PLT-4050
資源重複 409 業務冪等鍵已存在 CC-POST-4001、CC-POST-4002

與其他類別位之區別(選碼判準):

情境 用哪個類別位 理由
請求缺 X-Channel、JSON 壞掉、DELETE 不支援、路徑打錯 4 尚未進入業務邏輯,是「請求送錯了」
請求合法但業務規則拒絕(額度不足、點數不足) 1 已進入業務邏輯,是「業務上不允許」
樂觀鎖/冪等鍵衝突 2 併發語意
狀態機不允許轉換 3 狀態語意

⚠️ 不得用類別位 5(5xx)掩飾請求層錯誤——此即 CSP-003 §7 E3「用 500 掩飾所有錯誤」
所禁止之行為:前端無法區分、監控告警失準。請求層錯誤若回 500,每個客戶端誤用都會觸發
ERROR 告警
,造成告警疲勞,真正的伺服器異常將被淹沒。

實作歸屬:本類別位之 CC-PLT-4xxx 五碼由 common-spring 之 GlobalExceptionHandler
統一拋出
,服務不需自行實作。服務僅在需要更精確語意時才使用自有 4xxx 碼
(如 CC-PTY-4040「歸戶查無」)。Spring 例外之對應關係見 SDD-006 §3.4。

HTTP 狀態對應原則:

類別 HTTP 說明
請求不合法 400 標頭/參數缺失、主體格式錯誤、參數驗證失敗(類別位 4,CC-PLT-4001~4003)
客戶端輸入錯誤 400 / 422 欄位缺失(400)、商業規則拒絕(422,類別位 1)
身分/權限 401 / 403 未登入、無權限(CC-GW-1002/CC-GW-1003)
資源或路徑不存在 404 無對應路由、聚合根查無(類別位 4)
方法不支援 405 HTTP 動詞與端點不符(類別位 4,CC-PLT-4050)
商業衝突 409 冪等鍵衝突、版本衝突、狀態衝突、資源重複(類別位 2/3/4)
下游/依賴失敗 503 / 500 逾時、不可用(類別位 5)
系統內部 500 未預期(須告警,類別位 5)

服務代碼總表(摘要):AUTH(4) CARD(4) POST(4) LIM(3) PTY(3) PRD(3) PAY(3) STM(3) PRC(3) INS(3) RWD(3) CLR(3) DSP(3) MRC(3) NOT(3) RPT(3) MIG(3) PLT(3) GW(2)。不存在 CMN 等偽代碼。


3. 回應封套(統一格式)

所有回應(成功與失敗)一律套用統一信封(與 OpenAPI Envelope / Error schema 完全一致):

{
  "traceId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "data": { },
  "error": null
}

失敗時 data 為 null,error 攜帶結構化資訊:

{
  "traceId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "data": null,
  "error": {
    "code": "CC-LIM-1001",
    "message": "可用額度不足",
    "details": [
      { "field": "amount", "reason": "exceeds available limit" }
    ]
  }
}
欄位 型別 說明
traceId string 信封層追蹤號(ULID),成功/失敗皆帶
data object|null 成功為資料本體;失敗為 null
error.code string CC-<SVC4>-<NNNN>,須為 SDD-006 §3.4 登錄碼
error.message string 使用者可讀訊息
error.details[] array 欄位級原因(可多筆);非欄位錯誤可省略

禁止(對應 CSP-001 §8 B4):不得將例外堆疊、SQL 語句、框架內部錯誤原樣回傳。
內部錯誤一律轉為 CC-PLT-5xxx + 通用訊息,細節僅寫入伺服器日誌(含 traceId)。


4. 例外處理階層

classDiagram
    class DomainException {
        +String code
        +String message
    }
    class ValidationException
    class BusinessRuleException
    class ConcurrencyException
    class InfrastructureException
    class DownstreamException
    DomainException <|-- ValidationException
    DomainException <|-- BusinessRuleException
    DomainException <|-- ConcurrencyException
    DomainException <|-- InfrastructureException
    InfrastructureException <|-- DownstreamException
  • domain/ 拋出 DomainException 子類(業務語意),不含技術細節。
  • interfaces/ 設 ControllerAdvice 將 DomainException 映對為 HTTP 狀態+封套。
  • infrastructure/ 將 JDBC/HTTP 錯誤包裝為 InfrastructureException,不向上洩漏。

5. 冪等與重試

  1. 冪等鍵:所有寫側 Command(授權、入帳、繳款…)攜帶 Idempotency-Key( client 產生或上游傳入);
    重複提交返回首次結果,不重複執行(SDD-004 §幂等)。
  2. 重試:下游暫態失敗(503 服務不可用/外部逾時)由呼叫方以指數退避重試(上限 3 次,含 jitter);
    非暫態(4xx)不重試。
  3. 逾時:同步呼叫下游預設 800ms 逾時;超過即 CC-PLT-5001(外部系統逾時)並觸發 Stand-in / 補償(SDD-004)。

6. 交易與補償語意

  • 寫側一律在 application/ 層開交易;跨服務透過 Saga + Outbox(SDD-004)保證最終一致。
  • 補償失敗(如回退視窗逾時、補償步驟失敗)拋 CC-PLT-5002(Saga 補償失敗),由協調者告警並進入人工處置佇列;
    與遷移相關之失敗使用 CC-MIG-1001(遷移類型不支援)等 MIG 登錄碼,不得自創。

7. 禁止事項

編號 禁止 原因
E1 catch 後吞掉 失去可觀測性(CSP-001 B3)
E2 直接 throw e 給前端 洩漏內部(B4)
E3 用 500 掩飾所有錯誤 前端無法區分,監控告警失準
E4 回應含 SQL / 堆疊 / 連線字串 資安風險(SAD-005)
E5 以 Thread.sleep 當重試退避 佔用執行緒;應用排程/非同步重試

8. 變更記錄

版本 說明
1.0 初版:錯誤分類、回應封套、例外階層、冪等重試、禁止事項
1.1 與 SDD-006 §3.4 登錄表及 OpenAPI Error schema 對齊:①服務代碼改為 2–4 字母(原誤寫為 3 字母並引用不存在之 CC-CMN-*);②回應封套修正為 {traceId,data,error{details[]}},與 OpenAPI 完全一致(原 detail/timestamp/path 為錯誤形狀);③下游失敗狀態改為 503、補償失敗碼改為 CC-PLT-5002;④明訂錯誤碼唯一登錄表,Coder 不得自創
1.2 ①更正 §2 類別位 4 之代表碼:原引 CC-AUTH-4004 未登錄於 SDD-006 §3.4(且登錄表現無任何 404 碼),改引已登錄之 CC-POST-4001/CC-POST-4002;②§2 類別位 5 代表碼補入新增之 CC-PLT-5000。此二項係實作 common-spring 之 ErrorCodes(由 tools/gen_error_codes.py 自 SDD-006 §3.4 產生)時,以程式比對文件所發現
1.3 擴充 §2 類別位 4 語意為「請求與資源」(CR-004,2026-09-19 BEAM PMO 核定):
①類別位 4 之 HTTP 由「404/409」擴充為「400/404/405/409」,並新增 §2.1 明訂四個子語意與選碼判準(與類別位 1/2/3 之區別)。
②新增「請求不合法(400)」與「方法不支援(405)」兩列於「HTTP 狀態對應原則」表。
③動機(實測):CR-004 於 8089 實測 6 項情境、4 項錯誤——缺標頭、壞 JSON、不支援方法、路徑不存在全數回 500。根因:Spring MVC 之請求層例外皆非 DomainException 子類,落 GlobalExceptionHandler 兜底。原類別位 4 僅有 404/409,400 與 405 無處安放,若借用類別位 1(422 業務規則)或 5(5xx 基礎設施)皆為語意錯置。
④明訂選碼判準:類別位 4 之判準為「尚未進入業務邏輯」,並重申 §7 E3「不得用 500 掩飾錯誤」——請求層錯誤若回 500,每個客戶端誤用都會觸發 ERROR 告警(告警疲勞)。
⑤實作歸屬:CC-PLT-4xxx 五碼由 common-spring 之 GlobalExceptionHandler 統一拋出,服務不需自行實作(服務僅在需更精確語意時用自有 4xxx 碼)
(24-24/67)