Go中实现统一错误格式返回需封装结构化错误对象,定义含Code、Message、StatusCode等字段的AppError结构体,实现Error()方法,配合快捷构造函数、预定义错误变量、中间件统一拦截,确保前端可依据code分支处理、status code控制行为,后端便于日志监控。
在 Go 中实现统一错误格式返回,核心是避免直接用 errors.New 或 fmt.Errorf 返回裸错误,而是封装成结构化、可序列化、带上下文和状态码的错误对象,并在 HTTP 层统一拦截、标准化响应。
创建一个符合业务需要的错误类型,通常包含错误码、消息、HTTP 状态码、可选的详情字段(如请求 ID、时间戳):
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,
}
}
不 panic,不裸 return error,而是显式构造并提前返回结构化错误响应:
*AppError 并写入响应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 统一封装:
500 Internal Server Error
*AppError,直接渲染;否则兜底转为通用服务错误application/json,并设置正确 status codefunc 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 聚类分析。结构体设计要兼顾可读性、可扩展性和安全性(比如不把数据库错误堆栈直接透出)。