UNPKG

@xilil/error-codes

Version:

统一错误码管理库 - 支持多语言、类型安全的错误处理工具

670 lines (523 loc) 23.6 kB
# @aqcq/error-codes 这是一个用于统一管理 Express 项目集合中错误码的 TypeScript 库。该库提供了标准化的错误码定义、多语言支持和完整的错误处理工具。 ## 特性 - 🌐 **多语言支持**: 支持中文和英文错误消息 - 📋 **分类管理**: 按错误性质分类管理错误码(而非业务类型) - 🔧 **类型安全**: 完整的 TypeScript 类型定义 - 🚀 **零依赖**: 不依赖外部库 - 📊 **HTTP 状态码映射**: 错误码与 HTTP 状态码的智能映射 - 🛠️ **工具函数**: 丰富的错误处理工具函数 - 🎯 **Express 集成**: 提供 Express 错误处理中间件 ## 设计理念 ### 🎯 按错误性质分类,而非业务类型 传统的按业务分类(如 CHAT_ERROR_CODES、FILE_ERROR_CODES)存在以下问题: - **业务耦合严重**:新业务需要创建新的错误码分类 - **错误码重复**:相似错误在不同业务中被重复定义 - **维护成本高**:业务变更时需要修改错误码结构 - **复用性差**:错误码难以跨业务使用 ### ✅ 新的设计原则 1. **按错误性质分类**:根据错误的本质特征进行分类 2. **高度可复用**:同一错误码可在多个业务中使用 3. **易于维护**:错误码结构稳定,不随业务变化 4. **覆盖全面**:预先定义常见的错误场景 ## 错误码分类 ### 1xxx - 系统内部错误码 用于系统级别的错误: - `1001` **INTERNAL_ERROR** - 内部服务器错误 - `1002` **SERVICE_UNAVAILABLE** - 服务暂时不可用 - `1003` **CONFIGURATION_ERROR** - 系统配置错误 - `1004` **EXTERNAL_SERVICE_ERROR** - 外部服务调用失败 ### 2xxx - 参数验证错误码 用于请求参数验证相关的错误: - `2001` **INVALID_PARAMETER** - 参数无效 - `2002` **MISSING_PARAMETER** - 缺少必需参数 - `2003` **PARAMETER_OUT_OF_RANGE** - 参数超出范围 - `2004` **INVALID_FORMAT** - 格式不正确 - `2005` **CONTENT_TOO_LONG** - 内容过长 - `2006` **UNSUPPORTED_TYPE** - 不支持的类型 ### 4xxx - 认证授权错误码 用于身份认证和权限相关的错误: - `4001` **UNAUTHORIZED** - 未授权访问 - `4002` **INVALID_TOKEN** - 无效的令牌 - `4003` **TOKEN_EXPIRED** - 令牌已过期 - `4004` **INSUFFICIENT_PERMISSIONS** - 权限不足 - `4005` **ACCOUNT_DISABLED** - 账户已被禁用 - `4006` **INVALID_CREDENTIALS** - 认证信息无效 - `4007` **ACCOUNT_LOCKED** - 账户已锁定 - `4008` **SESSION_EXPIRED** - 会话已过期 - `4009` **ACCOUNT_NOT_FOUND** - 账户不存在 - `4010` **INSUFFICIENT_BALANCE** - 余额不足 - `4011` **INSUFFICIENT_INTEGRAL** - 积分不足 ### 41xx - 用户错误码 用于用户相关的错误 - `4001` **INVALID_INVITE_CODE** - 邀请码无效 ### 5xxx - 资源管理错误码 用于资源相关的错误: - `5001` **RESOURCE_NOT_FOUND** - 资源不存在 - `5002` **RESOURCE_ALREADY_EXISTS** - 资源已存在 - `5003` **RESOURCE_CREATION_FAILED** - 资源创建失败 - `5004` **RESOURCE_UPDATE_FAILED** - 资源更新失败 - `5005` **RESOURCE_DELETE_FAILED** - 资源删除失败 - `5006` **RESOURCE_IN_USE** - 资源正在使用中 - `5007` **RESOURCE_LOCKED** - 资源已被锁定 ### 6xxx - 配额限制错误码 用于各种限制和配额相关的错误: - `6001` **RATE_LIMIT_EXCEEDED** - 请求频率超出限制 - `6002` **QUOTA_EXCEEDED** - 配额已用完 - `6003` **STORAGE_QUOTA_EXCEEDED** - 存储空间不足 - `6004` **CONCURRENT_LIMIT_EXCEEDED** - 并发数超出限制 - `6005` **DAILY_LIMIT_EXCEEDED** - 日使用量超出限制 - `6006` **SIZE_LIMIT_EXCEEDED** - 大小超出限制 ### 7xxx - 数据处理错误码 用于数据处理相关的错误: - `7001` **DATA_CORRUPTION** - 数据已损坏 - `7002` **SERIALIZATION_ERROR** - 数据序列化失败 - `7003` **DESERIALIZATION_ERROR** - 数据反序列化失败 - `7004` **DATA_TRANSFORMATION_ERROR** - 数据转换失败 - `7005` **CHECKSUM_MISMATCH** - 校验和不匹配 ### 8xxx - 业务逻辑错误码 用于业务逻辑相关的错误: - `8001` **BUSINESS_RULE_VIOLATION** - 违反业务规则 - `8002` **OPERATION_NOT_ALLOWED** - 操作不被允许 - `8003` **PREREQUISITE_NOT_MET** - 前置条件未满足 - `8004` **WORKFLOW_ERROR** - 工作流错误 - `8005` **STATE_CONFLICT** - 状态冲突 ### 9xxx - 第三方服务错误码 用于第三方服务集成相关的错误: - `9001` **AI_SERVICE_ERROR** - AI 服务调用失败 - `9002` **AI_MODEL_UNAVAILABLE** - AI 模型不可用 - `9003` **PAYMENT_SERVICE_ERROR** - 支付服务错误 - `9004` **SMS_SERVICE_ERROR** - 短信服务错误 - `9005` **EMAIL_SERVICE_ERROR** - 邮件服务错误 - `9006` **CLOUD_STORAGE_ERROR** - 云存储服务错误 - `9007` **TRANSLATION_SERVICE_ERROR** - 翻译服务错误 - `9008` **WECHAT_API_ERROR** - 微信接口调用失败 ### 11xxx - AI 服务错误码 用于 AI 服务集成相关的错误: - `9001` **MODEL_NOT_AVAILABLE** - 模型不可用 ## 安装 ```bash npm install @aqcq/error-codes ``` ## 基本使用方法 ### 错误码查询 ```typescript import { getErrorCode, createErrorResponse, AppError } from '@sqcq/error-codes'; // 获取错误码信息 const error = getErrorCode('RESOURCE_NOT_FOUND', 'zh-CN'); console.log(error); // { code: 5001, message: '资源不存在', statusCode: 404, description: '请求的资源不存在' } // 创建错误响应 const response = createErrorResponse('UNAUTHORIZED', 'zh-CN', '/api/user'); console.log(response); // { success: false, code: 4001, message: '未授权访问', timestamp: 1699123456789, path: '/api/user' } // 抛出应用错误 throw new AppError('AI_SERVICE_ERROR', 'zh-CN'); ``` ### 跨业务复用示例 ```typescript import { RESOURCE_ERROR_CODES, VALIDATION_ERROR_CODES, AUTH_ERROR_CODES, createErrorResponse, } from '@sqcq/error-codes'; // 聊天业务中使用 function getChatConversation(id: string) { if (!id) { return createErrorResponse('MISSING_PARAMETER', 'zh-CN'); } if (!conversation) { return createErrorResponse('RESOURCE_NOT_FOUND', 'zh-CN'); } if (!hasPermission) { return createErrorResponse('INSUFFICIENT_PERMISSIONS', 'zh-CN'); } } // 文件业务中使用相同的错误码 function getFile(id: string) { if (!id) { return createErrorResponse('MISSING_PARAMETER', 'zh-CN'); } if (!file) { return createErrorResponse('RESOURCE_NOT_FOUND', 'zh-CN'); } if (!hasPermission) { return createErrorResponse('INSUFFICIENT_PERMISSIONS', 'zh-CN'); } } ``` ### 业务场景映射 不同业务场景可以使用相同的通用错误码: ```typescript // 聊天业务错误映射 const ChatServiceErrors = { CONVERSATION_NOT_FOUND: 'RESOURCE_NOT_FOUND', MESSAGE_TOO_LONG: 'CONTENT_TOO_LONG', AI_MODEL_UNAVAILABLE: 'AI_MODEL_UNAVAILABLE', DAILY_LIMIT_EXCEEDED: 'DAILY_LIMIT_EXCEEDED', } as const; // 文件业务错误映射 const FileServiceErrors = { FILE_NOT_FOUND: 'RESOURCE_NOT_FOUND', FILE_TOO_LARGE: 'SIZE_LIMIT_EXCEEDED', INVALID_FILE_TYPE: 'UNSUPPORTED_TYPE', STORAGE_QUOTA_EXCEEDED: 'STORAGE_QUOTA_EXCEEDED', } as const; // 用户业务错误映射 const UserServiceErrors = { USER_NOT_FOUND: 'RESOURCE_NOT_FOUND', USER_ALREADY_EXISTS: 'RESOURCE_ALREADY_EXISTS', INVALID_PASSWORD: 'INVALID_CREDENTIALS', ACCOUNT_LOCKED: 'ACCOUNT_LOCKED', } as const; ``` ## Express.js 错误处理 我们提供了专门为 Express 设计的错误处理中间件,让你可以直接通过 `throw` 抛出错误,自动返回标准化的错误响应给前端。 ### 基本用法 ```typescript import express from 'express'; import { AppError, expressErrorHandler, asyncHandler, throwError, throwIf, assert, createSuccessResponse, } from '@aqcq/error-codes'; const app = express(); app.use(express.json()); // 使用错误处理中间件(必须在所有路由之后) app.use(expressErrorHandler('zh-CN', true)); // 路由示例 app.get( '/api/users/:id', asyncHandler(async (req, res) => { const id = parseInt(req.params.id); // 方式1: 使用传统的 throw new AppError() if (isNaN(id)) { throw new AppError('INVALID_PARAMETER', 'zh-CN', true, { field: 'id' }); } const user = await findUser(id); if (!user) { throw new AppError('RESOURCE_NOT_FOUND', 'zh-CN', true, { userId: id }); } res.json(createSuccessResponse(user, '获取用户成功')); }) ); app.post( '/api/users', asyncHandler(async (req, res) => { const { name, email } = req.body; // 方式2: 使用 throwError() 简化抛出错误 if (!name) throwError('MISSING_PARAMETER', { field: 'name' }); if (!email) throwError('MISSING_PARAMETER', { field: 'email' }); // 方式3: 使用 throwIf() 条件抛出错误 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; throwIf(!emailRegex.test(email), 'INVALID_FORMAT', { field: 'email' }); const existingUser = await findUserByEmail(email); throwIf(!!existingUser, 'RESOURCE_ALREADY_EXISTS', { email }); const newUser = await createUser({ name, email }); res.status(201).json(createSuccessResponse(newUser, '创建用户成功')); }) ); app.delete( '/api/users/:id', asyncHandler(async (req, res) => { const id = parseInt(req.params.id); // 方式4: 使用 assert() 断言函数 assert(!isNaN(id), 'INVALID_PARAMETER', { field: 'id' }); const user = await findUser(id); assert(!!user, 'RESOURCE_NOT_FOUND', { userId: id }); await deleteUser(id); res.json(createSuccessResponse(null, '删除用户成功')); }) ); app.listen(3000); ``` ### 错误响应格式 所有错误都会返回统一的 JSON 格式: ```json { "success": false, "code": 5001, "message": "资源不存在", "timestamp": 1699123456789, "path": "/api/users/123", "data": { "userId": 123 } } ``` 成功响应格式: ```json { "success": true, "data": { "id": 1, "name": "张三" }, "message": "获取用户成功", "timestamp": 1699123456789 } ``` ## API 文档 ### 类型定义 ```typescript interface ErrorCode { code: number; message: string; statusCode: number; description?: string; } interface ErrorCodeConfig { code: number; messages: { 'zh-CN': string; 'en-US': string; }; statusCode: number; description?: string; } interface ErrorResponse { success: false; code: number; data?: any; message: string; timestamp: number; path?: string; } interface SuccessResponse<T = any> { success: true; code: number; data: T; message?: string; timestamp: number; } type SupportedLanguage = 'zh-CN' | 'en-US'; ``` ### 核心函数 #### `getErrorCode(errorKey: string, language?: SupportedLanguage): ErrorCode` 获取指定错误键的错误信息。 ```typescript const error = getErrorCode('RESOURCE_NOT_FOUND', 'zh-CN'); // { code: 5001, message: '资源不存在', statusCode: 404, description: '请求的资源不存在' } ``` #### `createErrorResponse(errorKey: string, language?: SupportedLanguage, path?: string, data?: any): ErrorResponse` 创建标准错误响应对象。 ```typescript const response = createErrorResponse('UNAUTHORIZED', 'zh-CN', '/api/user', { reason: 'token_expired', }); ``` #### `createSuccessResponse<T>(data: T, message?: string, language?: SupportedLanguage): SuccessResponse<T>` 创建标准成功响应对象。 ```typescript const response = createSuccessResponse({ id: 1, name: '张三' }, '获取成功'); ``` #### `AppError` 自定义错误类,继承自 Error。 ```typescript throw new AppError('RESOURCE_NOT_FOUND', 'zh-CN', true, { userId: 123 }); ``` #### Express 辅助函数 - `expressErrorHandler(defaultLanguage?, enableStackTrace?)` - Express 错误处理中间件 - `asyncHandler(fn)` - 异步错误包装器 - `throwError(errorKey, details?, language?)` - 快速抛出错误 - `throwIf(condition, errorKey, details?, language?)` - 条件抛出错误 - `assert(condition, errorKey, details?, language?)` - 断言函数 #### 工具函数 - `isOperationalError(error: Error): boolean` - 判断是否为操作性错误 - `isValidErrorKey(key: string): boolean` - 判断错误键是否有效 - `findErrorKeyByCode(code: number): string | undefined` - 根据错误码查找对应的错误键 - `getStatusCode(errorKey: string): number` - 获取错误键对应的 HTTP 状态码 - `getAllErrorCodes(): Record<string, ErrorCodeConfig>` - 获取所有错误码配置 ## 错误码完整列表 | 错误码 | 错误名称 | 中文描述 | 英文描述 | HTTP 状态码 | | -------------------- | ------------------------- | ---------------- | ------------------------------- | ----------- | | **系统内部错误码** | | 1001 | INTERNAL_ERROR | 内部服务器错误 | Internal server error | 500 | | 1002 | SERVICE_UNAVAILABLE | 服务暂时不可用 | Service temporarily unavailable | 503 | | 1003 | CONFIGURATION_ERROR | 系统配置错误 | System configuration error | 500 | | 1004 | EXTERNAL_SERVICE_ERROR | 外部服务调用失败 | External service call failed | 502 | | **参数验证错误码** | | 2001 | INVALID_PARAMETER | 参数无效 | Invalid parameter | 400 | | 2002 | MISSING_PARAMETER | 缺少必需参数 | Missing required parameter | 400 | | 2003 | PARAMETER_OUT_OF_RANGE | 参数超出范围 | Parameter out of range | 400 | | 2004 | INVALID_FORMAT | 格式不正确 | Invalid format | 400 | | 2005 | CONTENT_TOO_LONG | 内容过长 | Content too long | 400 | | 2006 | UNSUPPORTED_TYPE | 不支持的类型 | Unsupported type | 400 | | **认证授权错误码** | | 4001 | UNAUTHORIZED | 未授权访问 | Unauthorized access | 401 | | 4002 | INVALID_TOKEN | 无效的令牌 | Invalid token | 401 | | 4003 | TOKEN_EXPIRED | 令牌已过期 | Token expired | 401 | | 4004 | INSUFFICIENT_PERMISSIONS | 权限不足 | Insufficient permissions | 403 | | 4005 | ACCOUNT_DISABLED | 账户已被禁用 | Account disabled | 403 | | 4006 | INVALID_CREDENTIALS | 认证信息无效 | Invalid credentials | 401 | | 4007 | ACCOUNT_LOCKED | 账户已锁定 | Account locked | 423 | | 4008 | SESSION_EXPIRED | 会话已过期 | Session expired | 401 | | 4009 | ACCOUNT_NOT_FOUND | 用户不存在 | Account not found | 404 | | 4010 | INSUFFICIENT_BALANCE | 余额不足 | Insufficient balance | 400 | | 4011 | INSUFFICIENT_INTEGRAL | 积分不足 | Insufficient integral | 400 | | **用户错误码** | | 4101 | INVALID_INVITE_CODE | 邀请码无效 | Invalid invite code | 400 | | **资源管理错误码** | | 5001 | RESOURCE_NOT_FOUND | 资源不存在 | Resource not found | 404 | | 5002 | RESOURCE_ALREADY_EXISTS | 资源已存在 | Resource already exists | 409 | | 5003 | RESOURCE_CREATION_FAILED | 资源创建失败 | Resource creation failed | 500 | | 5004 | RESOURCE_UPDATE_FAILED | 资源更新失败 | Resource update failed | 500 | | 5005 | RESOURCE_DELETE_FAILED | 资源删除失败 | Resource deletion failed | 500 | | 5006 | RESOURCE_IN_USE | 资源正在使用中 | Resource is in use | 409 | | 5007 | RESOURCE_LOCKED | 资源已被锁定 | Resource is locked | 423 | | **配额限制错误码** | | 6001 | RATE_LIMIT_EXCEEDED | 请求频率超出限制 | Rate limit exceeded | 429 | | 6002 | QUOTA_EXCEEDED | 配额已用完 | Quota exceeded | 429 | | 6003 | STORAGE_QUOTA_EXCEEDED | 存储空间不足 | Storage quota exceeded | 507 | | 6004 | CONCURRENT_LIMIT_EXCEEDED | 并发数超出限制 | Concurrent limit exceeded | 429 | | 6005 | DAILY_LIMIT_EXCEEDED | 日使用量超出限制 | Daily limit exceeded | 429 | | 6006 | SIZE_LIMIT_EXCEEDED | 大小超出限制 | Size limit exceeded | 413 | | **数据处理错误码** | | 7001 | DATA_CORRUPTION | 数据已损坏 | Data corruption detected | 500 | | 7002 | SERIALIZATION_ERROR | 数据序列化失败 | Serialization failed | 500 | | 7003 | DESERIALIZATION_ERROR | 数据反序列化失败 | Deserialization failed | 400 | | 7004 | DATA_TRANSFORMATION_ERROR | 数据转换失败 | Data transformation failed | 500 | | 7005 | CHECKSUM_MISMATCH | 校验和不匹配 | Checksum mismatch | 400 | | **业务逻辑错误码** | | 8001 | BUSINESS_RULE_VIOLATION | 违反业务规则 | Business rule violation | 400 | | 8002 | OPERATION_NOT_ALLOWED | 操作不被允许 | Operation not allowed | 403 | | 8003 | PREREQUISITE_NOT_MET | 前置条件未满足 | Prerequisite not met | 412 | | 8004 | WORKFLOW_ERROR | 工作流错误 | Workflow error | 400 | | 8005 | STATE_CONFLICT | 状态冲突 | State conflict | 409 | | **第三方服务错误码** | | 9001 | AI_SERVICE_ERROR | AI 服务调用失败 | AI service error | 502 | | 9002 | AI_MODEL_UNAVAILABLE | AI 模型不可用 | AI model unavailable | 503 | | 9003 | PAYMENT_SERVICE_ERROR | 支付服务错误 | Payment service error | 502 | | 9004 | SMS_SERVICE_ERROR | 短信服务错误 | SMS service error | 502 | | 9005 | EMAIL_SERVICE_ERROR | 邮件服务错误 | Email service error | 502 | | 9006 | CLOUD_STORAGE_ERROR | 云存储服务错误 | Cloud storage error | 502 | | 9007 | TRANSLATION_SERVICE_ERROR | 翻译服务错误 | Translation service error | 502 | | 9008 | WECHAT_API_ERROR | 微信接口调用失败 | WeChat API error | 502 | | **AI 服务错误码** | | 11001 | MODEL_NOT_AVAILABLE | 模型不可用 | Model not available | 400 | ## 项目结构 ``` error-codes/ ├── src/ │ ├── constants/ │ │ ├── common.ts # 通用成功/错误码 │ │ ├── business.ts # 业务错误码(按性质分类) │ │ └── index.ts # 统一导出 │ ├── types/ │ │ └── index.ts # 类型定义 │ ├── utils/ │ │ └── index.ts # 工具函数和Express中间件 │ └── index.ts # 主入口 ├── examples/ # 使用示例 ├── __tests__/ # 测试文件 ├── package.json └── README.md # 说明文档 ``` ## 最佳实践 ### 1. 优先使用通用错误码 ```typescript // ✅ 推荐 - 使用通用错误码 return createErrorResponse('RESOURCE_NOT_FOUND'); // ❌ 不推荐 - 创建业务特定错误码 return createErrorResponse('CHAT_CONVERSATION_NOT_FOUND'); ``` ### 2. 根据错误性质选择错误码 ```typescript // 参数问题 -> 使用 2xxx 验证错误码 if (!isValidEmail(email)) { return createErrorResponse('INVALID_FORMAT'); } // 资源问题 -> 使用 5xxx 资源错误码 if (!user) { return createErrorResponse('RESOURCE_NOT_FOUND'); } // 权限问题 -> 使用 4xxx 认证错误码 if (!hasPermission) { return createErrorResponse('INSUFFICIENT_PERMISSIONS'); } ``` ### 3. 创建业务特定的错误映射 ```typescript // 为特定业务创建错误码映射,而不是创建新的错误码 export const ChatServiceErrors = { CONVERSATION_NOT_FOUND: 'RESOURCE_NOT_FOUND', MESSAGE_TOO_LONG: 'CONTENT_TOO_LONG', AI_SERVICE_ERROR: 'AI_SERVICE_ERROR', RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED', } as const; ``` ### 4. 使用 Express 中间件简化错误处理 ```typescript // ✅ 推荐 - 使用中间件和便捷函数 app.get( '/api/users/:id', asyncHandler(async (req, res) => { const id = parseInt(req.params.id); throwIf(isNaN(id), 'INVALID_PARAMETER', { field: 'id' }); const user = await findUser(id); throwIf(!user, 'RESOURCE_NOT_FOUND', { userId: id }); res.json(createSuccessResponse(user)); }) ); // ❌ 不推荐 - 手动处理每个错误 app.get('/api/users/:id', async (req, res) => { try { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ success: false, message: '参数无效', // ... 重复的错误处理代码 }); } // ... 更多重复代码 } catch (error) { // ... 手动错误处理 } }); ``` ## 扩展指南 当需要添加新的错误码时: 1. **首先考虑现有错误码是否适用** 2. **如果需要新错误码,按错误性质归类** 3. **避免创建业务特定的错误码分类** 4. **保持错误码的通用性和可复用性** 例如,如果需要添加新的限制类型错误: ```typescript // ✅ 添加到 QUOTA_ERROR_CODES MONTHLY_LIMIT_EXCEEDED: { code: 6007, messages: { 'zh-CN': '月使用量超出限制', 'en-US': 'Monthly limit exceeded' }, statusCode: 429, description: '月使用量已达到上限' } // ❌ 不要创建新的业务分类 VIDEO_SERVICE_ERROR_CODES: { // ... } ``` ## 开发 ```bash # 安装依赖 npm install # 运行测试 npm test # 构建 npm run build # 运行演示 npm run demo ``` ## 许可证 MIT License --- 通过按错误性质而非业务类型进行分类,我们实现了: - **高复用性**:同一错误码可在多个业务中使用 - **易维护性**:错误码结构稳定,不随业务变化 - **标准化**:统一的错误处理方式 - **可扩展性**:易于添加新的错误类型 这种设计让错误码系统更加健壮和可持续发展。