错误处理 #
概述 #
良好的错误处理是 API 设计的重要组成部分。清晰、一致的错误响应可以帮助开发者快速定位和解决问题。
text
┌─────────────────────────────────────────────────────────────┐
│ 错误处理的重要性 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 糟糕的错误响应: │
│ { │
│ "error": "Something went wrong" │
│ } │
│ │
│ 问题: │
│ ❌ 不知道发生了什么错误 │
│ ❌ 不知道如何修复 │
│ ❌ 无法调试 │
│ │
│ 良好的错误响应: │
│ { │
│ "error": { │
│ "code": "VALIDATION_ERROR", │
│ "message": "Invalid input data", │
│ "details": [ │
│ { │
│ "field": "email", │
│ "message": "Invalid email format" │
│ } │
│ ], │
│ "requestId": "req-123-456" │
│ } │
│ } │
│ │
│ 优点: │
│ ✅ 错误类型明确 │
│ ✅ 错误原因清晰 │
│ ✅ 提供修复建议 │
│ ✅ 便于追踪调试 │
│ │
└─────────────────────────────────────────────────────────────┘
错误响应格式 #
标准错误格式 #
text
┌─────────────────────────────────────────────────────────────┐
│ 标准错误格式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 基本结构: │
│ { │
│ "error": { │
│ "code": "ERROR_CODE", // 错误码 │
│ "message": "Human readable message", // 错误信息 │
│ "details": [...], // 详细错误信息 │
│ "requestId": "req-xxx", // 请求追踪 ID │
│ "documentation": "https://..." // 文档链接 │
│ } │
│ } │
│ │
│ 字段说明: │
│ - code:机器可读的错误码 │
│ - message:人类可读的错误信息 │
│ - details:详细错误信息(可选) │
│ - requestId:请求追踪 ID │
│ - documentation:相关文档链接(可选) │
│ │
└─────────────────────────────────────────────────────────────┘
错误响应示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 错误响应示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 验证错误(400 Bad Request): │
│ HTTP/1.1 400 Bad Request │
│ Content-Type: application/json │
│ │
│ { │
│ "error": { │
│ "code": "VALIDATION_ERROR", │
│ "message": "Request validation failed", │
│ "details": [ │
│ { │
│ "field": "email", │
│ "code": "INVALID_FORMAT", │
│ "message": "Invalid email format" │
│ }, │
│ { │
│ "field": "password", │
│ "code": "MIN_LENGTH", │
│ "message": "Password must be at least 8 characters"│
│ } │
│ ], │
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 认证错误(401 Unauthorized): │
│ HTTP/1.1 401 Unauthorized │
│ WWW-Authenticate: Bearer error="invalid_token" │
│ │
│ { │
│ "error": { │
│ "code": "UNAUTHORIZED", │
│ "message": "Authentication required", │
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 权限错误(403 Forbidden): │
│ HTTP/1.1 403 Forbidden │
│ │
│ { │
│ "error": { │
│ "code": "FORBIDDEN", │
│ "message": "You don't have permission to access this resource",│
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 资源不存在(404 Not Found): │
│ HTTP/1.1 404 Not Found │
│ │
│ { │
│ "error": { │
│ "code": "RESOURCE_NOT_FOUND", │
│ "message": "User not found", │
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 冲突错误(409 Conflict): │
│ HTTP/1.1 409 Conflict │
│ │
│ { │
│ "error": { │
│ "code": "RESOURCE_CONFLICT", │
│ "message": "Email already exists", │
│ "details": { │
│ "field": "email", │
│ "value": "user@example.com" │
│ }, │
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 速率限制(429 Too Many Requests): │
│ HTTP/1.1 429 Too Many Requests │
│ Retry-After: 60 │
│ │
│ { │
│ "error": { │
│ "code": "RATE_LIMIT_EXCEEDED", │
│ "message": "Rate limit exceeded", │
│ "details": { │
│ "limit": 100, │
│ "remaining": 0, │
│ "resetAt": "2025-03-29T12:00:00Z", │
│ "retryAfter": 60 │
│ }, │
│ "requestId": "req-550e8400-e29b-41d4-a716" │
│ } │
│ } │
│ │
│ 服务器错误(500 Internal Server Error): │
│ HTTP/1.1 500 Internal Server Error │
│ │
│ { │
│ "error": { │
│ "code": "INTERNAL_ERROR", │
│ "message": "An unexpected error occurred", │
│ "requestId": "req-550e8400-e29b-41d4-a716", │
│ "documentation": "https://docs.example.com/errors" │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
错误码设计 #
错误码规范 #
text
┌─────────────────────────────────────────────────────────────┐
│ 错误码设计规范 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 命名规范: │
│ - 使用大写字母和下划线 │
│ - 使用 SCREAMING_SNAKE_CASE │
│ - 语义清晰,易于理解 │
│ │
│ 格式建议: │
│ {CATEGORY}_{SPECIFIC_ERROR} │
│ │
│ 示例: │
│ VALIDATION_ERROR 验证错误 │
│ VALIDATION_REQUIRED 必填字段缺失 │
│ VALIDATION_INVALID_FORMAT 格式错误 │
│ AUTH_UNAUTHORIZED 未认证 │
│ AUTH_TOKEN_EXPIRED Token 过期 │
│ RESOURCE_NOT_FOUND 资源不存在 │
│ RESOURCE_CONFLICT 资源冲突 │
│ │
└─────────────────────────────────────────────────────────────┘
常用错误码 #
text
┌─────────────────────────────────────────────────────────────┐
│ 常用错误码 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 通用错误: │
│ INTERNAL_ERROR 内部错误 │
│ BAD_REQUEST 请求格式错误 │
│ NOT_FOUND 资源不存在 │
│ METHOD_NOT_ALLOWED 方法不允许 │
│ │
│ 认证授权错误: │
│ UNAUTHORIZED 未认证 │
│ TOKEN_EXPIRED Token 过期 │
│ TOKEN_INVALID Token 无效 │
│ FORBIDDEN 无权限 │
│ │
│ 验证错误: │
│ VALIDATION_ERROR 验证失败 │
│ REQUIRED_FIELD 必填字段缺失 │
│ INVALID_FORMAT 格式错误 │
│ INVALID_VALUE 值无效 │
│ VALUE_TOO_LONG 值过长 │
│ VALUE_TOO_SHORT 值过短 │
│ │
│ 业务错误: │
│ RESOURCE_NOT_FOUND 资源不存在 │
│ RESOURCE_CONFLICT 资源冲突 │
│ RESOURCE_DELETED 资源已删除 │
│ OPERATION_FAILED 操作失败 │
│ │
│ 限制错误: │
│ RATE_LIMIT_EXCEEDED 速率限制 │
│ QUOTA_EXCEEDED 配额超限 │
│ │
└─────────────────────────────────────────────────────────────┘
错误处理实现 #
错误处理中间件 #
text
┌─────────────────────────────────────────────────────────────┐
│ 错误处理中间件 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // 自定义错误类 │
│ class ApiError extends Error { │
│ constructor(statusCode, code, message, details = null) { │
│ super(message); │
│ this.statusCode = statusCode; │
│ this.code = code; │
│ this.details = details; │
│ } │
│ } │
│ │
│ // 常用错误工厂 │
│ const Errors = { │
│ badRequest: (message, details) => │
│ new ApiError(400, 'BAD_REQUEST', message, details), │
│ │
│ unauthorized: (message = 'Authentication required') => │
│ new ApiError(401, 'UNAUTHORIZED', message), │
│ │
│ forbidden: (message = 'Access denied') => │
│ new ApiError(403, 'FORBIDDEN', message), │
│ │
│ notFound: (resource = 'Resource') => │
│ new ApiError(404, 'NOT_FOUND', `${resource} not found`),│
│ │
│ conflict: (message, details) => │
│ new ApiError(409, 'CONFLICT', message, details), │
│ │
│ validationError: (details) => │
│ new ApiError(422, 'VALIDATION_ERROR', │
│ 'Validation failed', details), │
│ │
│ rateLimitExceeded: (retryAfter) => │
│ new ApiError(429, 'RATE_LIMIT_EXCEEDED', │
│ 'Rate limit exceeded', { retryAfter }), │
│ │
│ internal: (message = 'Internal server error') => │
│ new ApiError(500, 'INTERNAL_ERROR', message) │
│ }; │
│ │
│ // 错误处理中间件 │
│ function errorHandler(err, req, res, next) { │
│ const requestId = req.id || generateRequestId(); │
│ │
│ // 记录错误日志 │
│ console.error({ │
│ requestId, │
│ error: err.message, │
│ stack: err.stack, │
│ path: req.path, │
│ method: req.method │
│ }); │
│ │
│ // 处理已知错误 │
│ if (err instanceof ApiError) { │
│ return res.status(err.statusCode).json({ │
│ error: { │
│ code: err.code, │
│ message: err.message, │
│ details: err.details, │
│ requestId │
│ } │
│ }); │
│ } │
│ │
│ // 处理未知错误 │
│ res.status(500).json({ │
│ error: { │
│ code: 'INTERNAL_ERROR', │
│ message: 'An unexpected error occurred', │
│ requestId │
│ } │
│ }); │
│ } │
│ │
│ // 使用 │
│ app.get('/users/:id', (req, res, next) => { │
│ const user = findUser(req.params.id); │
│ if (!user) { │
│ return next(Errors.notFound('User')); │
│ } │
│ res.json(user); │
│ }); │
│ │
│ app.use(errorHandler); │
│ │
└─────────────────────────────────────────────────────────────┘
验证错误处理 #
text
┌─────────────────────────────────────────────────────────────┐
│ 验证错误处理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // 验证中间件 │
│ function validate(schema) { │
│ return (req, res, next) => { │
│ const { error, value } = schema.validate(req.body, { │
│ abortEarly: false, // 返回所有错误 │
│ allowUnknown: false │
│ }); │
│ │
│ if (error) { │
│ const details = error.details.map(err => ({ │
│ field: err.path.join('.'), │
│ code: err.type.toUpperCase().replace(/\./g, '_'), │
│ message: err.message │
│ })); │
│ │
│ return next(Errors.validationError(details)); │
│ } │
│ │
│ req.body = value; │
│ next(); │
│ }; │
│ } │
│ │
│ // 使用 │
│ const userSchema = Joi.object({ │
│ name: Joi.string().min(2).max(50).required(), │
│ email: Joi.string().email().required(), │
│ password: Joi.string().min(8).required() │
│ }); │
│ │
│ app.post('/users', validate(userSchema), createUser); │
│ │
│ // 错误响应 │
│ { │
│ "error": { │
│ "code": "VALIDATION_ERROR", │
│ "message": "Validation failed", │
│ "details": [ │
│ { │
│ "field": "email", │
│ "code": "STRING_EMAIL", │
│ "message": "\"email\" must be a valid email" │
│ }, │
│ { │
│ "field": "password", │
│ "code": "STRING_MIN", │
│ "message": "\"password\" length must be at least 8"│
│ } │
│ ], │
│ "requestId": "req-xxx" │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
错误处理最佳实践 #
text
┌─────────────────────────────────────────────────────────────┐
│ 错误处理最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 使用正确的 HTTP 状态码 │
│ - 400:客户端请求错误 │
│ - 401:未认证 │
│ - 403:无权限 │
│ - 404:资源不存在 │
│ - 422:验证失败 │
│ - 429:速率限制 │
│ - 500:服务器错误 │
│ │
│ 2. 提供有意义的错误信息 │
│ ✅ "Email format is invalid" │
│ ❌ "Invalid input" │
│ │
│ 3. 包含请求追踪 ID │
│ - 便于日志查询和问题定位 │
│ │
│ 4. 不要暴露敏感信息 │
│ ❌ "Database connection failed: password incorrect" │
│ ✅ "Internal server error" │
│ │
│ 5. 提供文档链接 │
│ - 帮助开发者了解更多信息 │
│ │
│ 6. 记录错误日志 │
│ - 记录完整的错误信息 │
│ - 包含请求上下文 │
│ │
│ 7. 区分开发和生产环境 │
│ - 开发环境:返回详细错误 │
│ - 生产环境:返回简化错误 │
│ │
└─────────────────────────────────────────────────────────────┘
错误处理检查清单 #
text
□ 错误响应格式
□ 使用统一的错误格式
□ 包含错误码
□ 包含错误信息
□ 包含请求追踪 ID
□ HTTP 状态码
□ 使用正确的状态码
□ 状态码与错误类型匹配
□ 错误信息
□ 信息清晰易懂
□ 提供修复建议
□ 不暴露敏感信息
□ 错误日志
□ 记录错误详情
□ 记录请求上下文
□ 便于问题追踪
□ 验证错误
□ 返回所有验证错误
□ 指明错误字段
□ 提供具体的错误原因
下一步 #
现在你已经了解了错误处理,接下来学习 最佳实践,深入了解 RESTful API 设计的最佳实践!
最后更新:2026-03-29