--- tags: [后端, Go, Gin, 错误处理, API设计] create time: 2026-04-28 00:15 --- # 业务错误码:JSON Body vs 响应头 ## 概述 讨论 API 返回业务错误码时,应该放在 JSON body 还是自定义 HTTP 响应头(如 `X-Biz-Code`),以及何时需要两者兼有。 ## 正文 ### 方案对比 | | JSON Body(`code` 字段) | 响应头(`X-Biz-Code`) | |---|---|---| | **优点** | 解析简单,所有语言/框架天然支持 | 中间件/网关不经解析 body 就能识别错误类型 | | **缺点** | 客户端必须先解析 body 才知道发生了什么 | 不是所有代理层都透传自定义头;某些 SDK 默认不暴露 headers | ```go // 方案 A:仅 JSON body c.JSON(http.StatusBadRequest, gin.H{ "code": 40001, "message": "参数校验失败", }) // 方案 B:同时写入 header + body c.Header("X-Biz-Code", strconv.Itoa(40001)) c.JSON(http.StatusBadRequest, gin.H{ "code": 40001, "message": "参数校验失败", }) ``` ### 面向前端(同团队) JSON body 就够了。前端代码是你写的,统一走 body 的 `code` 字段即可,加 header 反而增加复杂度。 ```typescript // 前端统一拦截器处理 axios.interceptors.response.use(null, (err) => { const bizCode = err.response?.data?.code; if (bizCode === 40001) showToast("请检查输入"); if (bizCode === 40101) navigateTo("/login"); return Promise.reject(err); }); ``` ### 面向第三方开发者 —— 推荐双通道 原因有三: 1. **网关 / 反向代理决策**——WAF、API Gateway 或熔断器通常只能看 HTTP 状态码和 header,无法解析 JSON body。`X-Biz-Code` 能让网关直接拦截特定错误(比如 `40001` 直接限流,而不打到你的服务)。 2. **SDK 封装友好**——第三方集成 SDK 后,可根据 header 快速区分重试策略:瞬时错误自动重试,确定错误直接抛出,无需先解 body。 3. **调试效率**——运维通过日志打印 `$http_x_biz_code` 就能一眼看到业务错误分布,不用额外解析请求体。 ### Gin 中的统一实现 ```go func handleBizError(c *gin.Context, httpCode, bizCode int, msg string) { c.Header("X-Biz-Code", strconv.Itoa(bizCode)) // ← header c.JSON(httpCode, gin.H{ // ← body "code": bizCode, "message": msg, }) } ``` 成本极小——多一行 `c.Header()` 而已,但对两边的使用者(前端 + 第三方)都照顾到了。 ## 关联笔记 - [[GIN/6-error-handling]] — 错误处理机制总览 - [[GIN/6-error-handling/q-c-error-nil-panic]] — c.Error(nil) 是否会导致 panic