# D8D Starter 错误处理规范 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 1.0 | 2025-09-15 | 初始错误处理规范 | Sarah (PO) | ## 1. 错误处理原则 ### 1.1 核心原则 - **一致性**: 所有错误响应格式统一 - **安全性**: 不泄露敏感信息到客户端 - **可操作性**: 错误信息应指导用户或开发者 - **可追溯性**: 错误应包含足够上下文用于调试 ### 1.2 错误分类 #### 按严重程度 - 🔴 **致命错误**: 系统无法继续运行 - 🟡 **业务错误**: 用户操作失败,可恢复 - 🔵 **客户端错误**: 用户输入或配置问题 - ⚪ **信息性错误**: 警告或提示信息 #### 按来源 - **验证错误**: 输入数据验证失败 - **业务逻辑错误**: 业务规则违反 - **系统错误**: 基础设施或第三方服务故障 - **网络错误**: 连接超时或中断 ## 2. 错误响应格式 ### 2.1 标准错误响应 ```typescript // 成功响应 { "success": true, "data": { /* 业务数据 */ }, "message": "操作成功" } // 错误响应 { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "输入数据验证失败", "details": [ { "field": "email", "message": "邮箱格式不正确" } ], "timestamp": "2025-09-15T10:30:00Z", "requestId": "req_1234567890" } } ``` ### 2.2 HTTP状态码映射 | 错误类型 | HTTP状态码 | 错误代码 | 描述 | |----------|------------|----------|------| | 验证错误 | 400 | `VALIDATION_ERROR` | 输入数据验证失败 | | 未授权 | 401 | `UNAUTHORIZED` | 需要认证 | | 权限不足 | 403 | `FORBIDDEN` | 权限不足 | | 资源不存在 | 404 | `NOT_FOUND` | 资源未找到 | | 业务错误 | 409 | `BUSINESS_ERROR` | 业务规则冲突 | | 系统错误 | 500 | `INTERNAL_ERROR` | 服务器内部错误 | | 服务不可用 | 503 | `SERVICE_UNAVAILABLE` | 服务暂时不可用 | ## 3. 错误代码规范 ### 3.1 错误代码命名约定 - 使用 `SCREAMING_SNAKE_CASE` - 前缀表示错误类别 - 后缀表示具体错误 ### 3.2 标准错误代码 #### 认证错误 (AUTH_*) ```typescript export const AUTH_ERRORS = { UNAUTHORIZED: 'AUTH_UNAUTHORIZED', INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS', TOKEN_EXPIRED: 'AUTH_TOKEN_EXPIRED', TOKEN_INVALID: 'AUTH_TOKEN_INVALID', PERMISSION_DENIED: 'AUTH_PERMISSION_DENIED' } as const; ``` #### 验证错误 (VALIDATION_*) ```typescript export const VALIDATION_ERRORS = { REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD', INVALID_EMAIL: 'VALIDATION_INVALID_EMAIL', INVALID_PASSWORD: 'VALIDATION_INVALID_PASSWORD', UNIQUE_CONSTRAINT: 'VALIDATION_UNIQUE_CONSTRAINT', OUT_OF_RANGE: 'VALIDATION_OUT_OF_RANGE' } as const; ``` #### 业务错误 (BUSINESS_*) ```typescript export const BUSINESS_ERRORS = { USER_NOT_FOUND: 'BUSINESS_USER_NOT_FOUND', INSUFFICIENT_BALANCE: 'BUSINESS_INSUFFICIENT_BALANCE', OPERATION_NOT_ALLOWED: 'BUSINESS_OPERATION_NOT_ALLOWED', RESOURCE_CONFLICT: 'BUSINESS_RESOURCE_CONFLICT' } as const; ``` #### 系统错误 (SYSTEM_*) ```typescript export const SYSTEM_ERRORS = { DATABASE_ERROR: 'SYSTEM_DATABASE_ERROR', NETWORK_ERROR: 'SYSTEM_NETWORK_ERROR', EXTERNAL_SERVICE_ERROR: 'SYSTEM_EXTERNAL_SERVICE_ERROR', CONFIGURATION_ERROR: 'SYSTEM_CONFIGURATION_ERROR' } as const; ``` ## 4. 实现指南 ### 4.1 后端错误处理 #### 错误类定义 ```typescript // src/shared/errors/AppError.ts export class AppError extends Error { public readonly code: string; public readonly statusCode: number; public readonly details?: any; public readonly timestamp: Date; constructor( code: string, message: string, statusCode: number = 500, details?: any ) { super(message); this.code = code; this.statusCode = statusCode; this.details = details; this.timestamp = new Date(); // 保持正确的堆栈跟踪 Error.captureStackTrace(this, this.constructor); } toJSON() { return { code: this.code, message: this.message, details: this.details, timestamp: this.timestamp.toISOString() }; } } // 特定错误类 export class ValidationError extends AppError { constructor(message: string, details?: any) { super('VALIDATION_ERROR', message, 400, details); } } export class NotFoundError extends AppError { constructor(resource: string, id?: string | number) { const message = id ? `${resource} with ID ${id} not found` : `${resource} not found`; super('NOT_FOUND', message, 404); } } ``` #### 错误处理中间件 ```typescript // src/server/middleware/errorHandler.ts import { Context } from 'hono'; import { AppError } from '../../shared/errors/AppError'; export async function errorHandler(err: Error, c: Context) { // 记录错误 console.error('Error:', { message: err.message, stack: err.stack, url: c.req.url, method: c.req.method, timestamp: new Date().toISOString() }); // AppError 实例 if (err instanceof AppError) { return c.json( { success: false, error: { code: err.code, message: err.message, details: err.details, timestamp: err.timestamp.toISOString(), requestId: c.get('requestId') } }, err.statusCode ); } // 未知错误(生产环境隐藏细节) const isProduction = process.env.NODE_ENV === 'production'; return c.json( { success: false, error: { code: 'INTERNAL_ERROR', message: isProduction ? 'Internal server error' : err.message, timestamp: new Date().toISOString(), requestId: c.get('requestId') } }, 500 ); } ``` #### 使用示例 ```typescript // 在服务中使用 export class UserService { async getUserById(id: number) { const user = await User.findOne({ where: { id } }); if (!user) { throw new NotFoundError('User', id); } return user; } async createUser(data: CreateUserDto) { // 验证输入 const errors = validateUserData(data); if (errors.length > 0) { throw new ValidationError('Invalid user data', errors); } // 检查唯一性 const existingUser = await User.findOne({ where: [{ email: data.email }, { username: data.username }] }); if (existingUser) { throw new BusinessError('USER_ALREADY_EXISTS', 'User with this email or username already exists', 409); } // 创建用户 try { return await User.create(data).save(); } catch (error) { throw new DatabaseError('Failed to create user', error); } } } ``` ### 4.2 前端错误处理 #### 错误处理钩子 ```typescript // src/client/hooks/useErrorHandler.ts import { toast } from 'sonner'; export function useErrorHandler() { const handleError = (error: unknown) => { console.error('Application error:', error); if (error instanceof Error) { // 显示用户友好的错误消息 toast.error(getUserFriendlyMessage(error)); } else { toast.error('发生未知错误,请重试'); } }; const getUserFriendlyMessage = (error: Error): string => { const message = error.message.toLowerCase(); if (message.includes('network')) { return '网络连接失败,请检查网络设置'; } if (message.includes('validation')) { return '输入数据验证失败,请检查表单'; } if (message.includes('permission') || message.includes('auth')) { return '权限不足,请重新登录'; } return error.message || '操作失败,请重试'; }; return { handleError, getUserFriendlyMessage }; } ``` #### API客户端错误处理 ```typescript // src/client/api.ts import { toast } from 'sonner'; const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => { try { const response = await axios.request({ url: url.toString(), method: init?.method || 'GET', headers: init?.headers, data: init?.body, }); // 处理错误响应 if (response.status >= 400) { const errorData = response.data?.error || {}; throw new ApiError( errorData.message || '请求失败', response.status, errorData.code, errorData.details ); } return createResponse(response); } catch (error) { if (axios.isAxiosError(error)) { const errorData = error.response?.data?.error || {}; throw new ApiError( errorData.message || error.message, error.response?.status || 500, errorData.code, errorData.details ); } throw error; } }; class ApiError extends Error { constructor( message: string, public status: number, public code?: string, public details?: any ) { super(message); this.name = 'ApiError'; } } ``` ## 5. 日志和监控 ### 5.1 错误日志格式 ```typescript // 错误日志示例 { "level": "error", "timestamp": "2025-09-15T10:30:00.000Z", "message": "User creation failed", "code": "VALIDATION_ERROR", "stack": "Error: Invalid email format...", "context": { "userId": 123, "email": "invalid-email", "requestId": "req_1234567890", "url": "/api/v1/users", "method": "POST" } } ``` ### 5.2 监控配置 ```typescript // 错误监控集成 import * as Sentry from '@sentry/node'; import * as Tracing from '@sentry/tracing'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, }); // 在错误处理中间件中 if (process.env.NODE_ENV === 'production') { Sentry.captureException(err, { extra: { url: c.req.url, method: c.req.method, requestId: c.get('requestId') } }); } ``` ## 6. 测试错误场景 ### 6.1 错误测试示例 ```typescript // 测试验证错误 describe('UserService - Error Handling', () => { it('should throw ValidationError for invalid email', async () => { const invalidData = { email: 'invalid', password: '123' }; await expect(userService.createUser(invalidData)) .rejects .toThrow(ValidationError); await expect(userService.createUser(invalidData)) .rejects .toMatchObject({ code: 'VALIDATION_ERROR', statusCode: 400 }); }); it('should throw NotFoundError for non-existent user', async () => { await expect(userService.getUserById(999)) .rejects .toThrow(NotFoundError); }); }); // 测试错误响应格式 describe('Error Handler Middleware', () => { it('should return formatted error response', async () => { const server = createTestServer(); const response = await server.inject({ method: 'GET', url: '/api/v1/users/999' }); expect(response.statusCode).toBe(404); expect(response.json()).toEqual({ success: false, error: { code: 'NOT_FOUND', message: 'User with ID 999 not found', timestamp: expect.any(String), requestId: expect.any(String) } }); }); }); ``` ## 7. 最佳实践清单 ### 7.1 错误处理检查清单 - [ ] 所有错误都有明确的错误代码 - [ ] 错误消息对用户友好且可操作 - [ ] 生产环境不泄露敏感信息 - [ ] 错误包含足够的调试上下文 - [ ] 错误响应格式统一 - [ ] 适当的HTTP状态码 - [ ] 错误日志记录完整 - [ ] 前端错误处理用户友好 - [ ] 测试覆盖错误场景 ### 7.2 安全注意事项 - ❌ 不要返回堆栈跟踪到客户端 - ❌ 不要泄露数据库错误细节 - ❌ 不要暴露敏感配置信息 - ✅ 使用通用的错误消息 - ✅ 记录详细错误到服务器日志 - ✅ 使用请求ID关联错误 --- **最后更新**: 2025-09-15 **维护者**: 开发团队 **文档状态**: 正式版