/** * 统一错误码(docs/工程规范/04-API与路由规范.md §4) * API-14:新增错误码必须集中登记在这里,禁止业务代码写裸数字。 */ export const ErrorCode = { /** 400xx 参数校验 */ PARAM_INVALID: 40001, /** 401xx 认证 */ UNAUTHENTICATED: 40101, TOKEN_EXPIRED: 40102, SIGNATURE_INVALID: 40103, /** 403xx 授权 */ PERMISSION_DENIED: 40301, OUT_OF_SCOPE: 40302, SPECIAL_PERMISSION_REQUIRED: 40303, SOD_VIOLATION: 40304, /** 404xx 不存在 */ NOT_FOUND: 40401, /** 409xx 冲突 */ VERSION_CONFLICT: 40901, DUPLICATE_SUBMIT: 40902, STATE_NOT_ALLOWED: 40903, /** 422xx 业务规则 */ BUSINESS_RULE_VIOLATION: 42201, COMPLIANCE_BLOCKED: 42202, COST_OVER_BUDGET: 42203, /** 429xx 限流 */ RATE_LIMITED: 42901, CONCURRENCY_LIMITED: 42902, /** 500xx 系统 */ INTERNAL_ERROR: 50001, DEPENDENCY_UNAVAILABLE: 50002, TIMEOUT: 50003, } as const; export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode]; /** 错误码 → HTTP 状态码(4xx/5xx 段由百位决定) */ export const ERROR_HTTP_STATUS: Readonly> = { [ErrorCode.PARAM_INVALID]: 400, [ErrorCode.UNAUTHENTICATED]: 401, [ErrorCode.TOKEN_EXPIRED]: 401, [ErrorCode.SIGNATURE_INVALID]: 401, [ErrorCode.PERMISSION_DENIED]: 403, [ErrorCode.OUT_OF_SCOPE]: 403, [ErrorCode.SPECIAL_PERMISSION_REQUIRED]: 403, [ErrorCode.SOD_VIOLATION]: 403, [ErrorCode.NOT_FOUND]: 404, [ErrorCode.VERSION_CONFLICT]: 409, [ErrorCode.DUPLICATE_SUBMIT]: 409, [ErrorCode.STATE_NOT_ALLOWED]: 409, [ErrorCode.BUSINESS_RULE_VIOLATION]: 422, [ErrorCode.COMPLIANCE_BLOCKED]: 422, [ErrorCode.COST_OVER_BUDGET]: 422, [ErrorCode.RATE_LIMITED]: 429, [ErrorCode.CONCURRENCY_LIMITED]: 429, [ErrorCode.INTERNAL_ERROR]: 500, [ErrorCode.DEPENDENCY_UNAVAILABLE]: 503, [ErrorCode.TIMEOUT]: 504, }; /** 默认中文提示(前端兜底展示;服务端可按场景覆盖 message) */ export const ERROR_MESSAGES: Readonly> = { [ErrorCode.PARAM_INVALID]: '参数缺失或格式错误', [ErrorCode.UNAUTHENTICATED]: '请先登录', [ErrorCode.TOKEN_EXPIRED]: '登录已过期,请重新登录', [ErrorCode.SIGNATURE_INVALID]: '签名校验失败', [ErrorCode.PERMISSION_DENIED]: '没有操作权限', [ErrorCode.OUT_OF_SCOPE]: '数据范围之外', [ErrorCode.SPECIAL_PERMISSION_REQUIRED]: '缺少特殊权限', [ErrorCode.SOD_VIOLATION]: '违反职责互斥,不能执行该操作', [ErrorCode.NOT_FOUND]: '资源不存在或已删除', [ErrorCode.VERSION_CONFLICT]: '数据已被他人修改,请刷新后重试', [ErrorCode.DUPLICATE_SUBMIT]: '请勿重复提交', [ErrorCode.STATE_NOT_ALLOWED]: '当前状态不允许该操作', [ErrorCode.BUSINESS_RULE_VIOLATION]: '业务校验未通过', [ErrorCode.COMPLIANCE_BLOCKED]: '内容触发合规拦截', [ErrorCode.COST_OVER_BUDGET]: '成本超出预算', [ErrorCode.RATE_LIMITED]: '请求过于频繁,请稍后再试', [ErrorCode.CONCURRENCY_LIMITED]: '并发任务已达上限', [ErrorCode.INTERNAL_ERROR]: '服务内部错误', [ErrorCode.DEPENDENCY_UNAVAILABLE]: '依赖服务不可用', [ErrorCode.TIMEOUT]: '请求超时', }; const ERROR_CODE_VALUES: ReadonlySet = new Set(Object.values(ErrorCode)); export function isErrorCode(value: number): value is ErrorCode { return ERROR_CODE_VALUES.has(value); } /** 数字错误码对应的 HTTP 状态;优先查表(502/503/504 等细分),非法码回退 500 */ export function httpStatusOf(code: number): number { if (isErrorCode(code)) { return ERROR_HTTP_STATUS[code]; } const segment = Math.floor(code / 100); return segment >= 400 && segment <= 599 ? segment : 500; }