如何在Golang中处理API调用错误_提供标准化错误响应

Go中处理API错误需统一分类、封装结构化响应并映射HTTP错误:定义APIError结构含code/status/message/details;用中间件统一处理panic和error;按4xx/5xx/业务错误语义化响应;集成validator聚合校验错误到details。

在 Go 中处理 API 调用错误并返回标准化响应,核心是统一错误分类、封装结构化响应体、并在 HTTP 层做一致的错误映射。不建议直接返回原始 error 或 panic,而应将错误转化为可预测的 JSON 响应(如 {"code": 400, "message": "invalid email", "details": {...}})。

定义标准化错误结构

创建一个可序列化的错误响应结构,包含状态码、业务码、用户提示和可选详情:

示例:

type APIError struct {
    Code    int         `json:"code"`    // HTTP 状态码(如 404)
    Status  string      `json:"status"`  // 如 "Not Found"
    Message string      `json:"message"` // 用户友好的提示
    Details interface{} `json:"details,omitempty"`
}

func (e *APIError) Error() string { return e.Message }

// 快捷构造函数
func NewBadRequest(msg string, details ...interface{}) *APIError {
    err := &APIError{
        Code:    http.StatusBadRequest,
        Status:  http.StatusText(http.StatusBadRequest),
        Message: msg,
    }
    if len(details) > 0 && details[0] != nil {
        err.Details = details[0]
    }
    return err
}

统一错误中间件或包装器

避免每个 handler 重复写 json.NewEncoder(w).Encode(...)。推荐使用中间件捕获 panic 和显式错误:

  • http.Handler 包装器,在 defer 中 recover panic,并转为 500 Internal Server Error
  • 约定 handler 返回 error(如 func(w http.ResponseWriter, r *http.Request) error),由包装器统一处理
  • 若返回非 nil error,检查是否为 *APIError;是则按其 Code/Message 渲染;否则视为未预期错误,记录日志并返回 500

区分错误来源并映射到语义化响应

不同错误应有明确归因,便于前端处理和监控:

  • 客户端错误(4xx):参数校验失败(validator 库)、缺失 header、格式错误 —— 用 NewBadRequestNewUnauthorized
  • 服务端错误(5xx):数据库超时、下游 API 不可用、空指针 panic —— 记录完整 error 栈,返回泛化提示(如 "service unavailable"),避免泄露内部信息
  • 业务逻辑错误(常复用 4xx):用户不存在、权限不足、余额不足 —— 使用专属业务码(如 "user_not_found")放入 Details 字段,方便前端分支处理

集成校验与错误收集

对请求体(如 json)做结构化校验时,不要只返回第一个错误。用 github.com/go-playground/validator/v10 收集全部字段错误,再聚合进 APIError.Details

type CreateUserRequest struct {
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"required,gte=0,lte=120"`
}

// 校验后
if err := validate.Struct(req); err != nil {
    errs := make(map[string]string)
    for _, e := range err.(validator.ValidationErrors) {
        errs[e.Field()] = e.Tag() + " validation failed"
    }
    return NewBadRequest("validation failed", errs)
}