CR #201 » CSP-003_錯誤處理與回應碼規範.md
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. 冪等與重試
-
冪等鍵:所有寫側 Command(授權、入帳、繳款…)攜帶
Idempotency-Key( client 產生或上游傳入);
重複提交返回首次結果,不重複執行(SDD-004 §幂等)。 -
重試:下游暫態失敗(503 服務不可用/外部逾時)由呼叫方以指數退避重試(上限 3 次,含 jitter);
非暫態(4xx)不重試。 -
逾時:同步呼叫下游預設 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 碼) |