Files
services/utils/error-codes.js
T

218 lines
7.2 KiB
JavaScript
Raw Normal View History

2026-06-03 14:15:55 +08:00
/**
* v2 API 统一错误码系统
*
* 设计原则:
* - code: 0 表示成功(与 HTTP 状态码解耦)
* - 非零 code 表示失败,按模块分段
* - 兼容 v1code: 200 = 成功),通过 v2 响应头区分版本
*
* 错误码分段:
* 0 — 成功
* 1xxx — 通用错误(参数/鉴权/权限/限流)
* 2xxx — 用户模块
* 3xxx — 商品模块
* 4xxx — 订单模块
* 5xxx — 购物车模块
* 6xxx — 库存模块
* 7xxx — 退款模块
* 8xxx — 积分模块
* 9xxx — 其他模块
*/
// ============ 通用错误码 1xxx ============
const SUCCESS = 0
const ERR_BAD_REQUEST = 1000 // 请求参数错误
const ERR_UNAUTHORIZED = 1001 // 未登录/Token 无效
const ERR_FORBIDDEN = 1002 // 无权限
const ERR_NOT_FOUND = 1003 // 资源不存在
const ERR_CONFLICT = 1004 // 资源冲突
const ERR_RATE_LIMIT = 1005 // 请求过于频繁
const ERR_INTERNAL = 1006 // 服务器内部错误
const ERR_VALIDATION = 1007 // 数据校验失败
const ERR_DEPRECATED = 1008 // 接口已废弃
// ============ 用户模块 2xxx ============
const ERR_USER_NOT_FOUND = 2001
const ERR_USER_PASSWORD = 2002 // 密码错误
const ERR_USER_DISABLED = 2003 // 账号已禁用
const ERR_USER_EXISTS = 2004 // 用户已存在
const ERR_USER_PHONE_INVALID = 2005
// ============ 商品模块 3xxx ============
const ERR_GOODS_NOT_FOUND = 3001
const ERR_GOODS_OFF_SHELF = 3002 // 商品已下架
const ERR_GOODS_STOCK_LOW = 3003 // 库存不足
const ERR_GOODS_NAME_DUPLICATE = 3004
// ============ 订单模块 4xxx ============
const ERR_ORDER_NOT_FOUND = 4001
const ERR_ORDER_STATUS = 4002 // 订单状态不允许此操作
const ERR_ORDER_EMPTY = 4003 // 订单为空
const ERR_ORDER_CANNOT_CANCEL = 4004
// ============ 购物车模块 5xxx ============
const ERR_CART_ITEM_NOT_FOUND = 5001
const ERR_CART_QUANTITY_INVALID = 5002
const ERR_CART_GOODS_OFF_SHELF = 5003
// ============ 库存模块 6xxx ============
const ERR_STOCK_NEGATIVE = 6001 // 库存不能为负
const ERR_STOCK_LOG_NOT_FOUND = 6002
// ============ 退款模块 7xxx ============
const ERR_REFUND_NOT_FOUND = 7001
const ERR_REFUND_AMOUNT_INVALID = 7002
const ERR_REFUND_DUPLICATE = 7003 // 已有待处理退款
const ERR_REFUND_STATUS = 7004 // 退款状态不允许此操作
// ============ 积分模块 8xxx ============
const ERR_POINTS_INSUFFICIENT = 8001
const ERR_POINTS_GOODS_NOT_FOUND = 8002
const ERR_POINTS_GOODS_OFF_SHELF = 8003
const ERR_POINTS_GOODS_NO_STOCK = 8004
const ERR_POINTS_DELTA_EXCEED = 8005 // 积分变动超限
// ============ 错误码映射表 ============
const ERROR_MESSAGES = {
[SUCCESS]: 'success',
[ERR_BAD_REQUEST]: '请求参数错误',
[ERR_UNAUTHORIZED]: '未登录或登录已过期',
[ERR_FORBIDDEN]: '无权限访问',
[ERR_NOT_FOUND]: '资源不存在',
[ERR_CONFLICT]: '资源冲突',
[ERR_RATE_LIMIT]: '请求过于频繁,请稍后再试',
[ERR_INTERNAL]: '服务器内部错误',
[ERR_VALIDATION]: '数据校验失败',
[ERR_DEPRECATED]: '接口已废弃',
[ERR_USER_NOT_FOUND]: '用户不存在',
[ERR_USER_PASSWORD]: '密码错误',
[ERR_USER_DISABLED]: '账号已禁用',
[ERR_USER_EXISTS]: '用户已存在',
[ERR_USER_PHONE_INVALID]: '手机号格式错误',
[ERR_GOODS_NOT_FOUND]: '商品不存在',
[ERR_GOODS_OFF_SHELF]: '商品已下架',
[ERR_GOODS_STOCK_LOW]: '库存不足',
[ERR_GOODS_NAME_DUPLICATE]: '商品名称已存在',
[ERR_ORDER_NOT_FOUND]: '订单不存在',
[ERR_ORDER_STATUS]: '订单状态不允许此操作',
[ERR_ORDER_EMPTY]: '订单为空',
[ERR_ORDER_CANNOT_CANCEL]: '订单无法取消',
[ERR_CART_ITEM_NOT_FOUND]: '购物车商品不存在',
[ERR_CART_QUANTITY_INVALID]: '数量无效',
[ERR_CART_GOODS_OFF_SHELF]: '商品已下架',
[ERR_STOCK_NEGATIVE]: '库存不能为负数',
[ERR_STOCK_LOG_NOT_FOUND]: '库存记录不存在',
[ERR_REFUND_NOT_FOUND]: '退款申请不存在',
[ERR_REFUND_AMOUNT_INVALID]: '退款金额无效',
[ERR_REFUND_DUPLICATE]: '已有待处理的退款申请',
[ERR_REFUND_STATUS]: '退款状态不允许此操作',
[ERR_POINTS_INSUFFICIENT]: '积分不足',
[ERR_POINTS_GOODS_NOT_FOUND]: '积分商品不存在',
[ERR_POINTS_GOODS_OFF_SHELF]: '积分商品已下架',
[ERR_POINTS_GOODS_NO_STOCK]: '积分商品库存不足',
[ERR_POINTS_DELTA_EXCEED]: '积分变动超出允许范围',
}
// ============ v1 错误码 → v2 映射 ============
const V1_TO_V2_MAP = {
200: SUCCESS,
400: ERR_BAD_REQUEST,
401: ERR_UNAUTHORIZED,
403: ERR_FORBIDDEN,
404: ERR_NOT_FOUND,
500: ERR_INTERNAL,
}
// ============ v2 错误码 → HTTP 状态码映射 ============
const CODE_TO_HTTP_STATUS = {
[SUCCESS]: 200,
[ERR_BAD_REQUEST]: 400,
[ERR_UNAUTHORIZED]: 401,
[ERR_FORBIDDEN]: 403,
[ERR_NOT_FOUND]: 404,
[ERR_CONFLICT]: 409,
[ERR_RATE_LIMIT]: 429,
[ERR_INTERNAL]: 500,
[ERR_VALIDATION]: 422,
[ERR_DEPRECATED]: 410,
}
// ============ 工具函数 ============
/**
* 生成 v2 标准响应
* @param {number} code - 错误码(0 = 成功)
* @param {*} data - 响应数据
* @param {string} [message] - 自定义消息(默认从映射表取)
* @returns {{ code: number, data: *, message: string }}
*/
function respond(code, data, message) {
return {
code,
data: code === SUCCESS ? data : null,
message: message || ERROR_MESSAGES[code] || '未知错误',
}
}
/**
* 成功响应快捷方法
*/
function success(data, message) {
return respond(SUCCESS, data, message)
}
/**
* 错误响应快捷方法
*/
function error(code, message) {
return respond(code, null, message)
}
/**
* 将 v1 风格响应转换为 v2 风格
* v1: { code: 200, data, message }
* v2: { code: 0, data, message }
*/
function fromV1(v1Body) {
if (!v1Body || typeof v1Body.code !== 'number') return v1Body
const v2Code = V1_TO_V2_MAP[v1Body.code] ?? v1Body.code
return {
code: v2Code,
data: v2Code === SUCCESS ? v1Body.data : null,
message: v1Body.message || ERROR_MESSAGES[v2Code] || '',
}
}
/**
* 获取 v2 错误码对应的 HTTP 状态码
*/
function toHttpStatus(code) {
return CODE_TO_HTTP_STATUS[code] || 400
}
module.exports = {
// 错误码常量
SUCCESS,
ERR_BAD_REQUEST, ERR_UNAUTHORIZED, ERR_FORBIDDEN, ERR_NOT_FOUND,
ERR_CONFLICT, ERR_RATE_LIMIT, ERR_INTERNAL, ERR_VALIDATION, ERR_DEPRECATED,
ERR_USER_NOT_FOUND, ERR_USER_PASSWORD, ERR_USER_DISABLED, ERR_USER_EXISTS, ERR_USER_PHONE_INVALID,
ERR_GOODS_NOT_FOUND, ERR_GOODS_OFF_SHELF, ERR_GOODS_STOCK_LOW, ERR_GOODS_NAME_DUPLICATE,
ERR_ORDER_NOT_FOUND, ERR_ORDER_STATUS, ERR_ORDER_EMPTY, ERR_ORDER_CANNOT_CANCEL,
ERR_CART_ITEM_NOT_FOUND, ERR_CART_QUANTITY_INVALID, ERR_CART_GOODS_OFF_SHELF,
ERR_STOCK_NEGATIVE, ERR_STOCK_LOG_NOT_FOUND,
ERR_REFUND_NOT_FOUND, ERR_REFUND_AMOUNT_INVALID, ERR_REFUND_DUPLICATE, ERR_REFUND_STATUS,
ERR_POINTS_INSUFFICIENT, ERR_POINTS_GOODS_NOT_FOUND, ERR_POINTS_GOODS_OFF_SHELF,
ERR_POINTS_GOODS_NO_STOCK, ERR_POINTS_DELTA_EXCEED,
// 映射表
ERROR_MESSAGES,
V1_TO_V2_MAP,
CODE_TO_HTTP_STATUS,
// 工具函数
respond,
success,
error,
fromV1,
toHttpStatus,
}