Files
cs-note/hzh/GIN/6-error-handling/q-biz-code-header.md
T
2026-05-24 11:42:38 +08:00

78 lines
2.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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