Golang公共API返回错误的规范建议

10次阅读

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

Golang 公共 API 返回错误的规范建议

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) + 自定义错误类型实现 IsUnwrap,再在响应层映射到对应 code

立即学习go 语言免费学习笔记(深入)”;

  • ErrValidationcode: 400(参数校验失败)
  • ErrUnauthorizedcode: 401(token 过期 / 无效)
  • ErrNotFoundcode: 404(资源不存在)
  • ErrInternalcode: 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,前端收不到 coderequest_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 二次。

text=ZqhQzanResources