错误码规范
核心原则
错误码必须按类型分类,使用数字编码,确保前后端一致,遵循 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
)错误码扩展规则
新增错误码时,遵循以下步骤:
- 确定错误类型:通用/认证/业务/数据/外部服务
- 选择千位数:根据类型选择 1xxx-5xxx
- 分配具体编号:同类型错误递增编号
- 业务错误细分:按模块分配百位数范围
go
// ✅ 正确 - 按规则扩展
const (
// 已有通用错误 1000-1005
ErrTimeout = 1006 // 新增通用错误
// 新增业务模块使用 3300-3399
ErrNewModuleNotFound = 3300
ErrNewModuleInvalid = 3301
)