错误处理 #
一、错误处理概述 #
1.1 错误类型 #
| 类型 | 说明 | 示例 |
|---|---|---|
| 绑定错误 | 请求参数绑定失败 | JSON格式错误 |
| 验证错误 | 数据验证失败 | 必填字段为空 |
| 业务错误 | 业务逻辑错误 | 用户已存在 |
| 系统错误 | 系统内部错误 | 数据库连接失败 |
1.2 错误处理原则 #
text
1. 明确错误类型
2. 返回友好的错误信息
3. 记录错误日志
4. 不暴露敏感信息
二、绑定错误处理 #
2.1 基本处理 #
go
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
2.2 区分错误类型 #
go
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
err := c.ShouldBindJSON(&user)
if err != nil {
// 检查是否是JSON语法错误
if syntaxErr, ok := err.(*json.SyntaxError); ok {
c.JSON(400, gin.H{
"code": 400,
"message": "JSON格式错误",
"offset": syntaxErr.Offset,
})
return
}
// 检查是否是类型错误
if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
c.JSON(400, gin.H{
"code": 400,
"message": "类型错误",
"field": typeErr.Field,
"type": typeErr.Type.String(),
})
return
}
// 检查是否是验证错误
var verr validator.ValidationErrors
if errors.As(err, &verr) {
c.JSON(400, gin.H{
"code": 400,
"message": "数据验证失败",
"errors": formatValidationErrors(verr),
})
return
}
// 其他错误
c.JSON(400, gin.H{
"code": 400,
"message": "请求参数错误",
})
return
}
c.JSON(200, user)
})
r.Run()
}
func formatValidationErrors(errs validator.ValidationErrors) map[string]string {
errors := make(map[string]string)
for _, err := range errs {
errors[err.Field()] = getErrorMsg(err)
}
return errors
}
2.3 统一绑定错误处理 #
go
func bindAndValidate(c *gin.Context, req interface{}) error {
if err := c.ShouldBindJSON(req); err != nil {
return NewBindError(err)
}
return nil
}
type BindError struct {
Err error
}
func NewBindError(err error) *BindError {
return &BindError{Err: err}
}
func (e *BindError) Error() string {
return e.Err.Error()
}
func (e *BindError) Response() gin.H {
// 处理验证错误
var verr validator.ValidationErrors
if errors.As(e.Err, &verr) {
errors := make(map[string]string)
for _, fe := range verr {
errors[fe.Field()] = getErrorMsg(fe)
}
return gin.H{
"code": 400,
"message": "数据验证失败",
"errors": errors,
}
}
// 处理JSON错误
if _, ok := e.Err.(*json.SyntaxError); ok {
return gin.H{
"code": 400,
"message": "JSON格式错误",
}
}
return gin.H{
"code": 400,
"message": "请求参数错误",
}
}
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := bindAndValidate(c, &user); err != nil {
if bindErr, ok := err.(*BindError); ok {
c.JSON(400, bindErr.Response())
return
}
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
三、业务错误处理 #
3.1 定义业务错误 #
go
type BusinessError struct {
Code int
Message string
}
func NewBusinessError(code int, message string) *BusinessError {
return &BusinessError{
Code: code,
Message: message,
}
}
func (e *BusinessError) Error() string {
return e.Message
}
// 预定义错误
var (
ErrUserNotFound = NewBusinessError(1001, "用户不存在")
ErrUserExists = NewBusinessError(1002, "用户已存在")
ErrInvalidPassword = NewBusinessError(1003, "密码错误")
ErrPermissionDenied = NewBusinessError(1004, "权限不足")
)
3.2 使用业务错误 #
go
func main() {
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
user, err := getUserByID(id)
if err != nil {
if err == ErrUserNotFound {
c.JSON(404, gin.H{
"code": err.(*BusinessError).Code,
"message": err.Error(),
})
return
}
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
3.3 统一业务错误处理 #
go
func main() {
r := gin.Default()
// 错误处理中间件
r.Use(ErrorHandlerMiddleware())
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
user, err := getUserByID(id)
if err != nil {
c.Error(err) // 记录错误
return
}
c.JSON(200, user)
})
r.Run()
}
func ErrorHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
// 处理业务错误
if businessErr, ok := err.(*BusinessError); ok {
c.JSON(400, gin.H{
"code": businessErr.Code,
"message": businessErr.Message,
})
return
}
// 处理其他错误
c.JSON(500, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}
}
四、统一错误响应 #
4.1 响应结构体 #
go
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Errors interface{} `json:"errors,omitempty"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(200, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Fail(c *gin.Context, code int, message string) {
c.JSON(200, Response{
Code: code,
Message: message,
})
}
func FailWithErrors(c *gin.Context, code int, message string, errors interface{}) {
c.JSON(200, Response{
Code: code,
Message: message,
Errors: errors,
})
}
4.2 使用示例 #
go
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
var verr validator.ValidationErrors
if errors.As(err, &verr) {
FailWithErrors(c, 400, "数据验证失败", formatErrors(verr))
return
}
Fail(c, 400, "请求参数错误")
return
}
if err := createUser(&user); err != nil {
Fail(c, 500, "创建用户失败")
return
}
Success(c, user)
})
r.Run()
}
4.3 错误码设计 #
go
const (
// 通用错误码 1xxx
CodeSuccess = 0
CodeBadRequest = 1000
CodeUnauthorized = 1001
CodeForbidden = 1003
CodeNotFound = 1004
CodeInternalError = 1005
// 用户相关错误码 2xxx
CodeUserNotFound = 2001
CodeUserExists = 2002
CodeInvalidPassword = 2003
CodeTokenExpired = 2004
CodeTokenInvalid = 2005
// 业务相关错误码 3xxx
CodeOrderNotFound = 3001
CodeOrderExpired = 3002
)
var errorMessages = map[int]string{
CodeSuccess: "成功",
CodeBadRequest: "请求参数错误",
CodeUnauthorized: "未授权",
CodeForbidden: "禁止访问",
CodeNotFound: "资源不存在",
CodeInternalError: "服务器内部错误",
CodeUserNotFound: "用户不存在",
CodeUserExists: "用户已存在",
CodeInvalidPassword: "密码错误",
CodeTokenExpired: "Token已过期",
CodeTokenInvalid: "Token无效",
}
五、错误日志记录 #
5.1 日志中间件 #
go
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
latency := time.Since(start)
statusCode := c.Writer.Status()
// 记录错误日志
if len(c.Errors) > 0 {
for _, err := range c.Errors {
log.Printf("[ERROR] %s %s %d %v error: %v",
c.Request.Method,
path,
statusCode,
latency,
err.Err,
)
}
}
}
}
5.2 错误恢复 #
go
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 记录堆栈信息
stack := debug.Stack()
log.Printf("[PANIC] %v\n%s", err, stack)
// 返回错误响应
c.AbortWithStatusJSON(500, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}()
c.Next()
}
}
5.3 结构化日志 #
go
import "go.uber.org/zap"
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
r := gin.New()
r.Use(func(c *gin.Context) {
start := time.Now()
c.Next()
logger.Info("request",
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", time.Since(start)),
zap.String("client_ip", c.ClientIP()),
zap.String("errors", c.Errors.String()),
)
})
r.Run()
}
六、错误处理最佳实践 #
6.1 错误包装 #
go
func main() {
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
user, err := getUserByID(id)
if err != nil {
// 包装错误,添加上下文
c.Error(fmt.Errorf("获取用户失败: %w", err))
return
}
c.JSON(200, user)
})
r.Run()
}
6.2 错误分类 #
go
type AppError struct {
Code int
Message string
StatusCode int
Err error
}
func (e *AppError) Error() string {
return e.Message
}
func (e *AppError) Unwrap() error {
return e.Err
}
var (
ErrBadRequest = &AppError{
Code: 1000,
Message: "请求参数错误",
StatusCode: 400,
}
ErrNotFound = &AppError{
Code: 1001,
Message: "资源不存在",
StatusCode: 404,
}
ErrInternal = &AppError{
Code: 1002,
Message: "服务器内部错误",
StatusCode: 500,
}
)
6.3 错误处理中间件 #
go
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) == 0 {
return
}
err := c.Errors.Last().Err
var appErr *AppError
if errors.As(err, &appErr) {
c.JSON(appErr.StatusCode, gin.H{
"code": appErr.Code,
"message": appErr.Message,
})
return
}
c.JSON(500, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}
七、总结 #
7.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 绑定错误 | 区分JSON错误和验证错误 |
| 业务错误 | 自定义错误类型 |
| 统一响应 | 标准化错误格式 |
| 错误日志 | 记录关键错误信息 |
7.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 友好提示 | 不暴露敏感信息 |
| 错误码 | 使用错误码标识错误 |
| 日志记录 | 记录错误上下文 |
| 错误恢复 | 捕获panic |
7.3 下一步 #
现在你已经掌握了错误处理,接下来让我们学习 模板基础,了解Gin的模板渲染!
最后更新:2026-03-28