Files
services/utils/error-codes.js
T
2026-06-03 14:15:55 +08:00

218 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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,
}