Skip to content

错误处理

VEF Framework 中的错误处理最佳实践。

API 响应

成功响应

go
// Return data
return api.Success(ctx, user)

// Return with message
return api.SuccessWithMessage(ctx, "User created successfully", user)

// Return empty success
return api.Success(ctx, nil)

错误响应

go
// General error
return api.Error(ctx, "Something went wrong")

// Not found
return api.NotFound(ctx, "User not found")

// Forbidden
return api.Forbidden(ctx, "Permission denied")

// Validation error
return api.ValidationError(ctx, validationErrors)

// Custom status code
return api.ErrorWithCode(ctx, 400, "Invalid request")

错误类型

定义业务错误

go
package errors

import "errors"

var (
    ErrUserNotFound     = errors.New("user not found")
    ErrInvalidPassword  = errors.New("invalid password")
    ErrEmailExists      = errors.New("email already exists")
    ErrInsufficientBalance = errors.New("insufficient balance")
)

使用业务错误

go
func (s *UserService) GetUser(id string) (*models.User, error) {
    var user models.User
    err := orm.DB().First(&user, "id = ?", id).Error
    
    if errors.Is(err, gorm.ErrRecordNotFound) {
        return nil, ErrUserNotFound
    }
    
    return &user, err
}

错误包装

添加上下文

go
import "fmt"

func (s *OrderService) CreateOrder(params OrderParams) (*models.Order, error) {
    user, err := s.userService.GetUser(params.UserId)
    if err != nil {
        return nil, fmt.Errorf("failed to get user: %w", err)
    }
    
    // ...
}

检查错误链

go
if errors.Is(err, ErrUserNotFound) {
    return api.NotFound(ctx, "User not found")
}

在 API 中处理错误

go
func (r *UserResource) CreateUser(ctx fiber.Ctx, params UserParams) error {
    user, err := r.userService.Create(params)
    
    if err != nil {
        // Handle specific errors
        if errors.Is(err, ErrEmailExists) {
            return api.ErrorWithCode(ctx, 409, "Email already exists")
        }
        
        if errors.Is(err, ErrInvalidPassword) {
            return api.ValidationError(ctx, map[string]string{
                "password": "Password does not meet requirements",
            })
        }
        
        // Log unexpected errors
        log.WithError(err).Error("Failed to create user")
        return api.Error(ctx, "Failed to create user")
    }
    
    return api.Success(ctx, user)
}

验证错误

使用验证标签

go
type UserParams struct {
    Username string `json:"username" validate:"required,min=3,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8"`
    Age      int    `json:"age" validate:"omitempty,gte=0,lte=150"`
}

自定义验证

go
func validateUserParams(params *UserParams) error {
    // Custom validation logic
    if strings.Contains(params.Username, " ") {
        return errors.New("username cannot contain spaces")
    }
    
    // Check password strength
    if !isStrongPassword(params.Password) {
        return errors.New("password is too weak")
    }
    
    return nil
}

事务错误处理

go
func (s *OrderService) CreateOrder(params OrderParams) (*models.Order, error) {
    var order *models.Order
    
    err := orm.Transaction(func(tx *gorm.DB) error {
        // Create order
        order = &models.Order{
            UserId: params.UserId,
            Amount: params.Amount,
        }
        if err := tx.Create(order).Error; err != nil {
            return fmt.Errorf("failed to create order: %w", err)
        }
        
        // Deduct balance
        result := tx.Model(&models.User{}).
            Where("id = ? AND balance >= ?", params.UserId, params.Amount).
            Update("balance", gorm.Expr("balance - ?", params.Amount))
        
        if result.RowsAffected == 0 {
            return ErrInsufficientBalance
        }
        
        return nil
    })
    
    if err != nil {
        return nil, err
    }
    
    return order, nil
}

全局错误处理

错误恢复中间件

go
func ErrorRecoveryMiddleware() fiber.Handler {
    return func(ctx *fiber.Ctx) error {
        defer func() {
            if r := recover(); r != nil {
                log.WithFields(log.Fields{
                    "panic":     r,
                    "stack":     string(debug.Stack()),
                    "path":      ctx.Path(),
                    "requestId": contextx.GetRequestId(ctx),
                }).Error("Panic recovered")
                
                ctx.Status(500).JSON(fiber.Map{
                    "success": false,
                    "message": "Internal server error",
                })
            }
        }()
        
        return ctx.Next()
    }
}

下一步

基于 Apache License 2.0 许可发布