Skip to content

错误码规范

核心原则

错误码必须按类型分类,使用数字编码,确保前后端一致,遵循 Ant Design Pro 响应格式。

允许的做法

错误码分类体系

按千位数划分错误类型

范围类型说明HTTP 状态码
1000-1999通用错误参数验证、资源操作400, 404, 429
2000-2999认证授权错误登录、Token、权限401, 403
3000-3999业务逻辑错误根据业务模块细分400, 404
4000-4999数据操作错误数据库错误500
5000-5999外部服务错误第三方 API 调用500

通用错误(1000-1999)

go
// ✅ 正确 - 通用错误定义
const (
    ErrInvalidParams     = 1000  // 参数错误
    ErrValidationFailed  = 1001  // 数据校验失败
    ErrResourceNotFound  = 1002  // 资源不存在
    ErrResourceExists    = 1003  // 资源已存在
    ErrOperationFailed   = 1004  // 操作失败
    ErrRateLimitExceeded = 1005  // 请求频率超限
)

认证授权错误(2000-2999)

go
// ✅ 正确 - 认证错误定义
const (
    ErrUnauthorized      = 2000  // 未认证
    ErrTokenInvalid      = 2001  // Token 无效
    ErrTokenExpired      = 2002  // Token 过期
    ErrPermissionDenied  = 2003  // 无权限
    ErrInvalidCredential = 2004  // 用户名或密码错误
    ErrAccountDisabled   = 2005  // 账号已禁用
    ErrAccountInactive   = 2006  // 账号未激活
)

业务错误分配原则(3000-3999)

业务错误按模块分配百位数范围,每个功能模块占用 100 个错误码。

模块错误码分配表

模块范围功能模块示例场景
3000-3099用户管理用户注册、登录、信息管理
3100-3199订单管理订单创建、支付、发货
3200-3299商品管理商品上架、库存、分类
3300-3399支付服务支付创建、退款、对账
3400-3499文件管理文件上传、下载、解析
3500-3599消息通知邮件、短信、推送通知
3600-3699评论系统评论发布、审核、举报
3700-3799权限管理角色、权限、授权

模块内错误码示例

用户管理模块(3000-3099)

go
const (
    ErrUserEmailExists       = 3000  // 邮箱已存在
    ErrUserEmailInvalid      = 3001  // 邮箱格式无效
    ErrUserPasswordWeak      = 3002  // 密码强度不足
    ErrUserNotFound          = 3003  // 用户不存在
    ErrUserCurrentPwdWrong   = 3004  // 当前密码错误
    ErrUserPasswordMismatch  = 3005  // 两次密码不一致
    ErrUserPhoneExists       = 3006  // 手机号已存在
    ErrUserPhoneInvalid      = 3007  // 手机号格式无效
    ErrUserCannotDelete      = 3008  // 用户存在关联数据无法删除
    ErrUserSelfDelete        = 3009  // 不能删除自己
)

订单管理模块(3100-3199)

go
const (
    ErrOrderNotFound        = 3100  // 订单不存在
    ErrOrderStatusInvalid   = 3101  // 订单状态不允许该操作
    ErrOrderAmountInvalid   = 3102  // 订单金额异常
    ErrOrderItemsEmpty      = 3103  // 订单项为空
    ErrOrderItemQtyInvalid  = 3104  // 订单项数量无效
    ErrOrderAlreadyPaid     = 3105  // 订单已支付
    ErrOrderAlreadyCanceled = 3106  // 订单已取消
    ErrOrderCannotRefund    = 3107  // 订单不允许退款
    ErrOrderExpired         = 3108  // 订单已过期
)

商品管理模块(3200-3299)

go
const (
    ErrProductNotFound      = 3200  // 商品不存在
    ErrProductNameDuplicate = 3201  // 商品名称重复
    ErrProductPriceInvalid  = 3202  // 价格无效
    ErrProductStockLow      = 3203  // 库存不足
    ErrProductOffline       = 3204  // 商品已下架
    ErrProductCategoryEmpty = 3205  // 商品分类为空
    ErrProductSkuNotFound   = 3206  // SKU 不存在
    ErrProductSkuDuplicate  = 3207  // SKU 重复
)

文件管理模块(3400-3499)

go
const (
    ErrFileFormatUnsupported = 3400  // 文件格式不支持
    ErrFileTooLarge          = 3401  // 文件过大
    ErrFileUploadFailed      = 3402  // 文件上传失败
    ErrFileParseFailed       = 3403  // 文件解析失败
    ErrFileHeaderInvalid     = 3404  // 文件表头格式不正确
    ErrFileContentEmpty      = 3405  // 文件内容为空
    ErrFileRowsExceeded      = 3406  // 文件行数超限
    ErrFileNotFound          = 3407  // 文件不存在
)

消息通知模块(3500-3599)

go
const (
    ErrEmailSendFailed       = 3500  // 邮件发送失败
    ErrSMSSendFailed         = 3501  // 短信发送失败
    ErrPushSendFailed        = 3502  // 推送通知失败
    ErrNotificationTemplate  = 3503  // 通知模板不存在
    ErrNotificationChannelInvalid = 3504  // 通知渠道无效
)

完整定义示例

go
// ✅ 正确 - 按模块清晰划分
const (
    // 用户管理模块 (3000-3099)
    ErrUserEmailExists      = 3000
    ErrUserEmailInvalid     = 3001
    ErrUserPasswordWeak     = 3002
    ErrUserNotFound         = 3003

    // 订单管理模块 (3100-3199)
    ErrOrderNotFound        = 3100
    ErrOrderStatusInvalid   = 3101
    ErrOrderAmountInvalid   = 3102

    // 商品管理模块 (3200-3299)
    ErrProductNotFound      = 3200
    ErrProductStockLow      = 3203
    ErrProductOffline       = 3204

    // 支付服务模块 (3300-3399)
    ErrPaymentFailed        = 3300
    ErrPaymentAmountInvalid = 3301
    ErrPaymentTimeout       = 3302
    ErrRefundFailed         = 3303
)

// ❌ 错误 - 无模块划分,难以扩展和维护
const (
    ErrSomeError1 = 3001
    ErrSomeError2 = 3002
    ErrSomeError3 = 3003
    // 无法看出这些错误码属于哪个模块
)

数据库错误(4000-4999)

go
// ✅ 正确 - 数据库错误定义
const (
    ErrDatabaseError   = 4000  // 数据库错误
    ErrRecordNotFound  = 4001  // 数据不存在
    ErrDuplicateKey    = 4002  // 数据重复
    ErrDatabaseTimeout = 4003  // 数据库超时
    ErrTransactionFail = 4004  // 事务失败
)

外部服务错误(5000-5999)

go
// ✅ 正确 - 外部服务错误定义
const (
    ErrExternalServiceError   = 5000  // 外部服务错误
    ErrExternalServiceTimeout = 5001  // 外部服务超时
)

Handler 层错误码使用

场景 1:用户注册

go
// ✅ 正确 - 针对不同错误返回对应错误码
func (h *UserHandler) Register(c echo.Context) error {
    var req RegisterRequest
    if err := c.Bind(&req); err != nil {
        return c.JSON(400, response.Fail(ErrInvalidParams, "参数错误"))
    }

    // 检查邮箱是否存在
    exists, _ := h.userService.EmailExists(c.Request().Context(), req.Email)
    if exists {
        return c.JSON(400, response.Fail(ErrUserEmailExists, "邮箱已存在"))
    }

    // 检查邮箱格式
    if !isValidEmail(req.Email) {
        return c.JSON(400, response.Fail(ErrUserEmailInvalid, "邮箱格式无效"))
    }

    // 检查密码强度
    if len(req.Password) < 8 {
        return c.JSON(400, response.Fail(ErrUserPasswordWeak, "密码长度至少8位"))
    }

    // 创建用户
    user, err := h.userService.Create(c.Request().Context(), &req)
    if err != nil {
        return c.JSON(500, response.Fail(ErrDatabaseError, "创建失败"))
    }

    return c.JSON(201, response.Success(user))
}

场景 2:订单创建

go
// ✅ 正确 - 订单业务错误码使用
func (h *OrderHandler) Create(c echo.Context) error {
    var req CreateOrderRequest
    if err := c.Bind(&req); err != nil {
        return c.JSON(400, response.Fail(ErrInvalidParams, "参数错误"))
    }

    // 检查订单项
    if len(req.Items) == 0 {
        return c.JSON(400, response.Fail(ErrOrderItemsEmpty, "订单项为空"))
    }

    // 检查商品库存
    for _, item := range req.Items {
        product, err := h.productService.GetByID(c.Request().Context(), item.ProductID)
        if err != nil {
            return c.JSON(404, response.Fail(ErrProductNotFound, "商品不存在"))
        }

        if product.Stock < item.Quantity {
            return c.JSON(400, response.Fail(ErrProductStockLow, "库存不足"))
        }

        if product.Status != "online" {
            return c.JSON(400, response.Fail(ErrProductOffline, "商品已下架"))
        }
    }

    // 创建订单
    order, err := h.orderService.Create(c.Request().Context(), &req)
    if err != nil {
        return c.JSON(500, response.Fail(ErrDatabaseError, "订单创建失败"))
    }

    return c.JSON(201, response.Success(order))
}

场景 3:文件上传

go
// ✅ 正确 - 文件上传错误码使用
func (h *FileHandler) Upload(c echo.Context) error {
    file, err := c.FormFile("file")
    if err != nil {
        return c.JSON(400, response.Fail(ErrInvalidParams, "文件获取失败"))
    }

    // 检查文件格式
    if !isAllowedFileType(file.Filename) {
        return c.JSON(400, response.Fail(ErrFileFormatUnsupported, "文件格式不支持"))
    }

    // 检查文件大小 (10MB)
    if file.Size > 10*1024*1024 {
        return c.JSON(400, response.Fail(ErrFileTooLarge, "文件过大,最大10MB"))
    }

    // 上传到对象存储
    src, _ := file.Open()
    defer src.Close()

    result, err := h.storageClient.Upload(c.Request().Context(), file.Filename, src)
    if err != nil {
        return c.JSON(500, response.Fail(ErrFileUploadFailed, "文件上传失败"))
    }

    return c.JSON(200, response.Success(result))
}

响应格式

Go 实现

go
// ✅ 正确 - 错误响应结构(Ant Design Pro 格式)
type ErrorResponse struct {
    Success      bool   `json:"success"`
    ErrorCode    int    `json:"errorCode"`
    ErrorMessage string `json:"errorMessage"`
}

// 错误消息映射
var errorMessages = map[int]string{
    ErrInvalidParams:    "参数错误",
    ErrUnauthorized:     "未认证",
    ErrResourceNotFound: "资源不存在",
}

// Handler 层错误处理
func (h *Handler) Get(c echo.Context) error {
    id, _ := strconv.ParseInt(c.Param("id"), 10, 64)

    data, err := h.service.GetByID(c.Request().Context(), id)
    if err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return c.JSON(http.StatusNotFound, ErrorResponse{
                Success:      false,
                ErrorCode:    ErrResourceNotFound,
                ErrorMessage: errorMessages[ErrResourceNotFound],
            })
        }
        return c.JSON(http.StatusInternalServerError, ErrorResponse{
            Success:      false,
            ErrorCode:    ErrDatabaseError,
            ErrorMessage: errorMessages[ErrDatabaseError],
        })
    }

    return c.JSON(http.StatusOK, Response{
        Success: true,
        Data:    data,
    })
}

字段校验错误

go
// ✅ 正确 - 字段校验错误响应
type ValidationErrorResponse struct {
    Success      bool             `json:"success"`
    ErrorCode    int              `json:"errorCode"`
    ErrorMessage string           `json:"errorMessage"`
    Errors       []ValidationError `json:"errors"`
}

type ValidationError struct {
    Field   string `json:"field"`
    Message string `json:"message"`
}

// Handler 层处理
func (h *Handler) Create(c echo.Context) error {
    var req CreateRequest
    if err := c.Bind(&req); err != nil {
        return c.JSON(http.StatusBadRequest, ErrorResponse{
            Success:      false,
            ErrorCode:    ErrInvalidParams,
            ErrorMessage: errorMessages[ErrInvalidParams],
        })
    }

    if err := c.Validate(&req); err != nil {
        return c.JSON(http.StatusBadRequest, ValidationErrorResponse{
            Success:      false,
            ErrorCode:    ErrValidationFailed,
            ErrorMessage: "参数校验失败",
            Errors: []ValidationError{
                {Field: "email", Message: "邮箱格式不正确"},
                {Field: "password", Message: "密码长度至少8位"},
            },
        })
    }

    // 创建逻辑...
}

TypeScript 实现

typescript
// ✅ 正确 - 错误码定义
export const ErrorCode = {
  // 通用错误
  INVALID_PARAMS: 1000,
  VALIDATION_FAILED: 1001,
  RESOURCE_NOT_FOUND: 1002,
  RESOURCE_EXISTS: 1003,

  // 认证错误
  UNAUTHORIZED: 2000,
  TOKEN_INVALID: 2001,
  TOKEN_EXPIRED: 2002,
  PERMISSION_DENIED: 2003,

  // 业务错误(根据实际业务定义)
  MODULE_A_NOT_FOUND: 3000,
  MODULE_B_NOT_FOUND: 3100,
} as const;

export const ErrorMessage: Record<number, string> = {
  [ErrorCode.INVALID_PARAMS]: '参数错误',
  [ErrorCode.UNAUTHORIZED]: '未认证',
  [ErrorCode.RESOURCE_NOT_FOUND]: '资源不存在',
};

// 错误处理
import { message } from 'antd';

const { data } = await service.getById(123);
if (!data.success) {
  const msg = ErrorMessage[data.errorCode] || '未知错误';
  message.error(msg);
}

错误码与 HTTP 状态码映射

go
// ✅ 正确 - 错误码映射到 HTTP 状态码
func getHTTPStatus(errorCode int) int {
    switch {
    case errorCode == ErrInvalidParams, errorCode == ErrValidationFailed:
        return http.StatusBadRequest
    case errorCode == ErrResourceNotFound:
        return http.StatusNotFound
    case errorCode == ErrUnauthorized, errorCode == ErrTokenExpired:
        return http.StatusUnauthorized
    case errorCode == ErrPermissionDenied:
        return http.StatusForbidden
    case errorCode == ErrRateLimitExceeded:
        return http.StatusTooManyRequests
    default:
        return http.StatusInternalServerError
    }
}

禁止的做法

  • 禁止 使用字符串错误码
  • 禁止 错误码重复定义
  • 避免 跨类型使用错误码范围
go
// ❌ 错误 - 字符串错误码
type ErrorResponse struct {
    ErrorCode string `json:"error_code"`  // 应使用数字
}

// ❌ 错误 - 错误码冲突
const (
    ErrTypeA = 3000
    ErrTypeB = 3000  // 重复
)

// ❌ 错误 - 业务错误使用认证范围
const (
    ErrBusinessLogic = 2500  // 应使用 3xxx
)

错误码扩展规则

新增错误码时,遵循以下步骤

  1. 确定错误类型:通用/认证/业务/数据/外部服务
  2. 选择千位数:根据类型选择 1xxx-5xxx
  3. 分配具体编号:同类型错误递增编号
  4. 业务错误细分:按模块分配百位数范围
go
// ✅ 正确 - 按规则扩展
const (
    // 已有通用错误 1000-1005
    ErrTimeout = 1006  // 新增通用错误

    // 新增业务模块使用 3300-3399
    ErrNewModuleNotFound = 3300
    ErrNewModuleInvalid  = 3301
)

相关文档