错误处理 #
一、错误类型 #
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