响应结构体必须同时含 code 和 status 字段,code 是五位数字业务码(如10001),status 是标准http状态码(如400);统一用 msg 字段,data 保持 interface{} 类型,request_id 必须透传;所有响应走封装函数 response.success/fail,禁用 gin.h 和默认 recovery,错误收敛至 response 实例。

响应结构体必须同时含 code 和 status 字段
前端只认 code(业务码)做逻辑分支,网关和监控系统依赖 status(HTTP 状态码)做重试或告警。两者不能混用,也不能省略任意一个。
-
code是 int 类型五位数字,比如10001表示「用户模块-手机号格式错误」,必须从pkg/errcode中导出常量,禁止硬编码 -
status是标准 HTTP 状态码,如400、404、500,由错误类型决定,不是固定写死 - 响应结构里不要出现
message、error、msg三种命名混用,统一用msg(小写) - 敏感字段如
data要加omitempty标签,空值时不输出;request_id必须透传,便于链路追踪
别用 gin.H 直接拼响应,定义 struct 并复用
每次写 c.JSON(400, gin.H{"code": 10001, "msg": "xxx"}) 是最危险的重复劳动——字段名不一致、缺 status、漏 request_id,时间一长就崩。
- 新建
pkg/response/response.go,定义type Response struct,字段为Code int、Msg string、Data interface{}、Status int、RequestID string - 所有成功/失败出口都走封装函数,如
response.Success(c, data)或response.Fail(c, http.StatusBadRequest, errcode.InvalidParam, "参数错误") - 函数内部统一注入
c.GetString("trace_id")到RequestID字段,避免 handler 里到处取 - 不要给
Data加指针类型约束(如*User),保持interface{}灵活性
c.Error() 不是响应动作,只是错误信号
很多人以为调了 c.Error(err) 就等于返回了错误,其实它只把 err 推进 c.Errors 队列,不写响应、不中断流程、也不带任何格式 —— 它纯属日志和调试用。
- 检查错误必须用
len(c.Errors) > 0,而不是err != nil;c.Errors.Last()才是最终要处理的那个 error - 业务 error 类型建议实现
interface{ Status() int },这样中间件能自动映射 HTTP 状态码,不用 switch 写一堆 case - 别在多个中间件里都调
c.AbortWithStatusJSON(),否则触发http: multiple response.WriteHeader callspanic - 错误工厂函数(如
errcode.NewBadRequest(errcode.UserNotFound))应返回 *AppError,带Code、Msg、HTTPStatus三要素
CustomRecovery 必须前置注册且 return 不可省
默认 gin.Recovery() 返回 HTML 页面,和你的 JSON 响应结构完全割裂,还可能和自定义错误中间件打架导致 panic。必须禁用它,换 CustomRecovery。
- 注册顺序必须是第一个:
r.Use(CustomRecovery(), ...),否则 panic 时其他中间件已写过 header -
defer里的return是关键,漏掉就会执行c.Next()导致二次响应 - panic 错误要用
log.Printf("PANIC: %+v", err)记录完整堆栈,别用fmt.Printf—— 后者丢堆栈 - 响应体里至少包含
code、msg、request_id,不要裸写map[string]interface{},复用上面定义的Response结构体
c.Error()、panic、validator 失败、中间件提前 abort——最后都收敛到同一个 Response 实例上,且每个字段来源清晰、不可绕过。这需要从中间件注册顺序、error 类型设计、到 handler 编写习惯全链路对齐。











