Go中实现统一错误格式返回需封装结构化错误对象,定义含Code、Message、StatusCode等字段的AppError结构体,实现Error()方法,配合快捷构造函数、预定义错误变量、中间件统一拦截,确保前端可依据code分支处理、status code控制行为,后端便于日志监控。

在 Go 中实现统一错误格式返回,核心是避免直接用 errors.New 或 fmt.Errorf 返回裸错误,而是封装成结构化、可序列化、带上下文和状态码的错误对象,并在 HTTP 层统一拦截、标准化响应。
定义统一错误结构体
创建一个符合业务需要的错误类型,通常包含错误码、消息、HTTP 状态码、可选的详情字段(如请求 ID、时间戳):
- 使用结构体而非接口,便于 JSON 序列化和字段扩展
- 实现
error接口的Error()方法,供日志或调试使用 - 推荐导出关键字段(如
Code,Message,StatusCode),方便中间件读取
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
StatusCode int `json:"status_code"`
RequestID string `json:"request_id,omitempty"`
}
func (e *AppError) Error() string {
return fmt.Sprintf("code=%d message=%s", e.Code, e.Message)
}
// 快捷构造函数
func NewAppError(code int, msg string, statusCode int) *AppError {
return &AppError{
Code: code,
Message: msg,
StatusCode: statusCode,
}
}
封装常用错误类型
按业务场景预定义错误变量或工厂函数,避免硬编码字符串和状态码:
- 定义标准错误码常量(如
ErrInvalidParam = 1001) - 提供带默认状态码的构造方法(如
BadRequest,NotFound,InternalError) - 支持链式追加上下文(例如用
Wrap包装底层 error,但保持结构体主体不变)
var (
ErrInvalidParam = NewAppError(1001, "invalid parameter", http.StatusBadRequest)
ErrNotFound = NewAppError(1004, "resource not found", http.StatusNotFound)
)
func WrapAppError(err error, appErr *AppError) *AppError {
if err == nil {
return appErr
}
return &AppError{
Code: appErr.Code,
Message: fmt.Sprintf("%s: %v", appErr.Message, err),
StatusCode: appErr.StatusCode,
RequestID: appErr.RequestID,
}
}
在 Handler 中主动返回统一错误
不 panic,不裸 return error,而是显式构造并提前返回结构化错误响应:
立即学习“go语言免费学习笔记(深入)”;
- 校验失败时直接构造
*AppError并写入响应 - 调用下游服务出错时,将原始 error 转为业务错误(避免暴露内部细节)
- 避免在多层函数中层层传递 error 再集中处理——Go 鼓励“快速失败”,尽早返回
func GetUser(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
renderError(w, ErrInvalidParam)
return
}
user, err := userService.Get(id)
if err != nil {
renderError(w, ErrNotFound)
return
}
renderJSON(w, http.StatusOK, user)
}
使用中间件统一捕获与响应
对未被 handler 显式处理的 panic 或未预期 error,用 recover + middleware 统一封装:
- 在顶层中间件中 defer recover,将 panic 转为
500 Internal Server Error - 若 handler 返回的是
*AppError,直接渲染;否则兜底转为通用服务错误 - 确保响应 Content-Type 为
application/json,并设置正确 status code
func ErrorHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
err := NewAppError(5000, "internal server error", http.StatusInternalServerError)
renderError(w, err)
}
}()
next.ServeHTTP(w, r)
})
}
func renderError(w http.ResponseWriter, err *AppError) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(err.StatusCode)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"error": err,
})
}
不复杂但容易忽略的是:统一错误格式不只是“长得一样”,关键是让前端能靠 code 做逻辑分支、靠 status code 控制重试或跳转、靠 message 做用户提示,同时后端日志和监控能按 code 聚类分析。结构体设计要兼顾可读性、可扩展性和安全性(比如不把数据库错误堆栈直接透出)。










