应统一错误响应结构为含code、message、request_id的APIError,禁用http.Error;通过中间件+context透传request_id;panic时recover并走统一错误流程,同时校验ctx.Err()防止二次panic。

Go HTTP handler 中如何统一返回错误结构
直接用 http.Error 或裸写 JSON 会导致前端难解析、日志难聚合、错误码不一致。建议所有公共 API 的错误响应都走同一结构体,且必须包含 code(业务码)、message(用户提示)、request_id(用于排查)。
常见错误是把 error 值直接序列化进 JSON,结果出现 "error": "EOF" 这类不可读内容;或漏掉 Content-Type: application/json 导致前端解析失败。
- 定义统一错误响应结构,例如:
type APIError struct { Code int `json:"code"` Message string `json:"message"` RequestID string `json:"request_id,omitempty"` } - 在中间件或封装的
WriteJSON方法中统一处理:遇到error类型时,自动转成APIError并设状态码(如400对应参数错误,500对应未预期错误) - 禁止在 handler 里调用
http.Error;它不支持自定义字段,且默认 Content-Type 是text/plain
区分 error 类型:业务错误 vs 系统错误 vs 验证错误
前端需要根据 code 做不同动作(比如重试、跳登录页、弹提示),所以不能全塞 500 或只用 errors.New 包裹字符串。
推荐用带类型标签的错误,比如用 fmt.Errorf("invalid token: %w", err) + 自定义错误类型实现 Is 和 Unwrap,再在响应层映射到对应 code:
立即学习“go语言免费学习笔记(深入)”;
-
ErrValidation→code: 400(参数校验失败) -
ErrUnauthorized→code: 401(token 过期/无效) -
ErrNotFound→code: 404(资源不存在) -
ErrInternal→code: 500(服务端 panic 或 DB 超时等)
避免把数据库错误(如 "pq: duplicate key violates unique constraint")直接透出给前端——这类信息要被拦截并转为更友好的 message,同时记录完整原始 error 到日志。
request_id 怎么生成和透传
没有 request_id,线上报错时根本没法对齐日志。它必须在入口(如 http.ServeHTTP 第一行)就生成,并贯穿整个请求生命周期。
- 用
uuid.NewString()或rand.String(12)生成,不要用时间戳或递增 ID(并发不安全、易猜测) - 存入
context.Context,后续所有日志、DB 查询、下游调用都要带上它 - 响应头里加
X-Request-ID,响应体 JSON 里也放一份request_id字段,方便前端上报问题时提供上下文 - 如果用了 Gin/Echo,别依赖框架默认的
X-Request-ID中间件——确认它是否真会写入响应体;很多只写 header 不写 body
panic 后如何安全 fallback 到错误响应
Go HTTP server 遇到 panic 默认会返回 500 Internal Server Error 和空 body,前端收不到 code 和 request_id,监控也抓不到上下文。
必须用 recover() 拦住 panic,并强制走统一错误响应流程:
- 在最外层 middleware 里 defer recover,捕获后调用统一错误写入函数
- 恢复后仍要打印 stacktrace 到日志(用
debug.PrintStack()或runtime/debug.Stack()),但绝不返回给前端 - 注意:recover 只对当前 goroutine 有效;如果业务启了新 goroutine(如
go fn()),需各自加 recover
最容易被忽略的是 context 超时或取消后继续写响应——recover 之后还得检查 ctx.Err() != nil,否则可能 panic 恢复了,但连接已断,再写 JSON 会 panic 二次。










