错误处理 #

一、错误类型 #

1.1 HTTPError #

Echo内置HTTPError类型:

go
type HTTPError struct {
    Code     int
    Message  interface{}
    Internal error
}

1.2 创建错误 #

go
echo.NewHTTPError(http.StatusNotFound, "资源不存在")

echo.NewHTTPError(http.StatusBadRequest, "参数错误")

echo.NewHTTPError(http.StatusUnauthorized, "请登录")

二、返回错误 #

2.1 基本用法 #

go
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    user, err := getUserByID(id)
    if err != nil {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    return c.JSON(http.StatusOK, user)
})

2.2 带内部错误 #

go
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    user, err := getUserByID(id)
    if err != nil {
        he := echo.NewHTTPError(http.StatusNotFound, "用户不存在")
        he.Internal = err
        return he
    }
    
    return c.JSON(http.StatusOK, user)
})

2.3 验证错误 #

go
e.POST("/users", func(c echo.Context) error {
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "参数格式错误")
    }
    
    if err := c.Validate(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    return c.JSON(http.StatusCreated, u)
})

三、全局错误处理 #

3.1 自定义错误处理器 #

go
e.HTTPErrorHandler = func(err error, c echo.Context) {
    code := http.StatusInternalServerError
    message := "Internal Server Error"
    
    if he, ok := err.(*echo.HTTPError); ok {
        code = he.Code
        message = he.Message.(string)
    }
    
    c.JSON(code, map[string]interface{}{
        "code":    code,
        "message": message,
    })
}

3.2 统一响应格式 #

go
type ErrorResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Details interface{} `json:"details,omitempty"`
}

e.HTTPErrorHandler = func(err error, c echo.Context) {
    var response ErrorResponse
    
    if he, ok := err.(*echo.HTTPError); ok {
        response = ErrorResponse{
            Code:    he.Code,
            Message: he.Message.(string),
        }
    } else {
        response = ErrorResponse{
            Code:    http.StatusInternalServerError,
            Message: "服务器内部错误",
        }
    }
    
    if !c.Response().Committed {
        c.JSON(response.Code, response)
    }
}

3.3 错误日志 #

go
e.HTTPErrorHandler = func(err error, c echo.Context) {
    code := http.StatusInternalServerError
    message := "Internal Server Error"
    
    if he, ok := err.(*echo.HTTPError); ok {
        code = he.Code
        message = he.Message.(string)
        
        if he.Internal != nil {
            log.Printf("Error: %v, Internal: %v", err, he.Internal)
        }
    } else {
        log.Printf("Error: %v", err)
    }
    
    c.JSON(code, map[string]interface{}{
        "code":    code,
        "message": message,
    })
}

四、自定义错误类型 #

4.1 定义错误类型 #

go
type AppError struct {
    Code    int
    Message string
    Details interface{}
}

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

func NewAppError(code int, message string, details interface{}) *AppError {
    return &AppError{
        Code:    code,
        Message: message,
        Details: details,
    }
}

4.2 使用自定义错误 #

go
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    user, err := getUserByID(id)
    if err != nil {
        return NewAppError(http.StatusNotFound, "用户不存在", map[string]string{
            "id": id,
        })
    }
    
    return c.JSON(http.StatusOK, user)
})

4.3 处理自定义错误 #

go
e.HTTPErrorHandler = func(err error, c echo.Context) {
    var response ErrorResponse
    
    switch e := err.(type) {
    case *AppError:
        response = ErrorResponse{
            Code:    e.Code,
            Message: e.Message,
            Details: e.Details,
        }
    case *echo.HTTPError:
        response = ErrorResponse{
            Code:    e.Code,
            Message: e.Message.(string),
        }
    default:
        response = ErrorResponse{
            Code:    http.StatusInternalServerError,
            Message: "服务器内部错误",
        }
    }
    
    c.JSON(response.Code, response)
}

五、错误中间件 #

5.1 错误恢复中间件 #

go
e.Use(middleware.Recover())

5.2 自定义恢复中间件 #

go
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Panic recovered: %v", r)
                c.JSON(http.StatusInternalServerError, map[string]string{
                    "error": "服务器内部错误",
                })
            }
        }()
        return next(c)
    }
})

5.3 错误包装中间件 #

go
func errorHandlerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        err := next(c)
        
        if err == nil {
            return nil
        }
        
        if !c.Response().Committed {
            return c.JSON(http.StatusInternalServerError, map[string]interface{}{
                "code":    500,
                "message": err.Error(),
            })
        }
        
        return err
    }
}

六、验证错误处理 #

6.1 验证错误格式化 #

go
func formatValidationErrors(err error) map[string]string {
    errors := make(map[string]string)
    
    for _, fe := range err.(validator.ValidationErrors) {
        field := fe.Field()
        tag := fe.Tag()
        
        switch tag {
        case "required":
            errors[field] = fmt.Sprintf("%s是必填字段", field)
        case "email":
            errors[field] = fmt.Sprintf("%s必须是有效的邮箱地址", field)
        case "min":
            errors[field] = fmt.Sprintf("%s长度不能小于%s", field, fe.Param())
        case "max":
            errors[field] = fmt.Sprintf("%s长度不能大于%s", field, fe.Param())
        default:
            errors[field] = fmt.Sprintf("%s验证失败", field)
        }
    }
    
    return errors
}

6.2 处理验证错误 #

go
e.POST("/users", func(c echo.Context) error {
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "参数格式错误")
    }
    
    if err := c.Validate(u); err != nil {
        errors := formatValidationErrors(err)
        return c.JSON(http.StatusBadRequest, map[string]interface{}{
            "code":    400,
            "message": "验证失败",
            "errors":  errors,
        })
    }
    
    return c.JSON(http.StatusCreated, u)
})

七、错误页面 #

7.1 HTML错误页面 #

go
e.HTTPErrorHandler = func(err error, c echo.Context) {
    code := http.StatusInternalServerError
    if he, ok := err.(*echo.HTTPError); ok {
        code = he.Code
    }
    
    errorPage := fmt.Sprintf("errors/%d.html", code)
    
    if err := c.Render(code, errorPage, map[string]interface{}{
        "code": code,
    }); err != nil {
        c.String(code, "Error")
    }
}

7.2 错误页面模板 #

views/errors/404.html

html
<!DOCTYPE html>
<html>
<head>
    <title>404 - 页面未找到</title>
</head>
<body>
    <h1>404</h1>
    <p>抱歉,您访问的页面不存在</p>
    <a href="/">返回首页</a>
</body>
</html>

views/errors/500.html

html
<!DOCTYPE html>
<html>
<head>
    <title>500 - 服务器错误</title>
</head>
<body>
    <h1>500</h1>
    <p>服务器内部错误,请稍后再试</p>
    <a href="/">返回首页</a>
</body>
</html>

八、完整示例 #

go
package main

import (
    "fmt"
    "log"
    "net/http"
    "github.com/go-playground/validator/v10"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

type User struct {
    Name     string `json:"name" validate:"required"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=6"`
}

type ErrorResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Details interface{} `json:"details,omitempty"`
}

type AppError struct {
    Code    int
    Message string
    Details interface{}
}

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

type CustomValidator struct {
    validator *validator.Validate
}

func (cv *CustomValidator) Validate(i interface{}) error {
    return cv.validator.Struct(i)
}

func main() {
    e := echo.New()
    
    e.Validator = &CustomValidator{validator: validator.New()}
    
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    
    e.HTTPErrorHandler = customErrorHandler
    
    e.GET("/users/:id", getUser)
    e.POST("/users", createUser)
    
    e.Logger.Fatal(e.Start(":8080"))
}

func customErrorHandler(err error, c echo.Context) {
    var response ErrorResponse
    
    switch e := err.(type) {
    case *AppError:
        response = ErrorResponse{
            Code:    e.Code,
            Message: e.Message,
            Details: e.Details,
        }
    case *echo.HTTPError:
        response = ErrorResponse{
            Code:    e.Code,
            Message: fmt.Sprintf("%v", e.Message),
        }
    default:
        response = ErrorResponse{
            Code:    http.StatusInternalServerError,
            Message: "服务器内部错误",
        }
    }
    
    log.Printf("Error: %v", err)
    
    if !c.Response().Committed {
        c.JSON(response.Code, response)
    }
}

func getUser(c echo.Context) error {
    id := c.Param("id")
    
    if id == "0" {
        return &AppError{
            Code:    http.StatusNotFound,
            Message: "用户不存在",
            Details: map[string]string{"id": id},
        }
    }
    
    return c.JSON(http.StatusOK, map[string]string{
        "id":   id,
        "name": "张三",
    })
}

func createUser(c echo.Context) error {
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "参数格式错误")
    }
    
    if err := c.Validate(u); err != nil {
        errors := formatValidationErrors(err)
        return c.JSON(http.StatusBadRequest, map[string]interface{}{
            "code":    400,
            "message": "验证失败",
            "details": errors,
        })
    }
    
    return c.JSON(http.StatusCreated, u)
}

func formatValidationErrors(err error) map[string]string {
    errors := make(map[string]string)
    
    for _, fe := range err.(validator.ValidationErrors) {
        field := fe.Field()
        tag := fe.Tag()
        
        switch tag {
        case "required":
            errors[field] = fmt.Sprintf("%s是必填字段", field)
        case "email":
            errors[field] = fmt.Sprintf("%s必须是有效的邮箱地址", field)
        case "min":
            errors[field] = fmt.Sprintf("%s长度不能小于%s", field, fe.Param())
        default:
            errors[field] = fmt.Sprintf("%s验证失败", field)
        }
    }
    
    return errors
}

九、总结 #

错误处理要点:

要点 说明
HTTPError Echo内置错误类型
NewHTTPError 创建HTTP错误
HTTPErrorHandler 全局错误处理器
自定义错误 实现error接口
错误中间件 Recover恢复panic
验证错误 格式化错误信息

准备好学习JWT认证了吗?让我们进入下一章!

最后更新:2026-03-28