# Data Model: Fiber 错误处理集成 **Feature**: 003-error-handling **Date**: 2025-11-14 **Status**: Draft ## 概述 本文档定义了 Fiber 错误处理集成所需的数据模型和结构。由于这是一个基础设施功能,主要涉及错误处理流程,没有持久化的数据实体,但有运行时的数据结构。 ## 核心数据结构 ### 1. AppError (应用错误类型) **位置**: `pkg/errors/errors.go` (已存在,需扩展) **用途**: 表示应用层的业务错误,包含错误码、消息和原始错误链 **字段**: ```go type AppError struct { Code int // 应用错误码 (1000-1999: 客户端错误, 2000-2999: 服务端错误) Message string // 错误消息 (用户可见,已脱敏) HTTPStatus int // HTTP 状态码 (根据 Code 自动映射) Err error // 底层原始错误 (可选,用于错误链) } ``` **方法**: ```go // Error 实现 error 接口 func (e *AppError) Error() string // Unwrap 支持错误链 func (e *AppError) Unwrap() error // WithHTTPStatus 设置自定义 HTTP 状态码 func (e *AppError) WithHTTPStatus(status int) *AppError ``` **验证规则**: - Code 必须在定义的范围内 (1000-2999) - Message 不能为空 - HTTPStatus 如果未设置,根据 Code 自动映射 **关系**: - 无数据库关系 (运行时对象) - 可以包装其他 error 形成错误链 --- ### 2. ErrorResponse (错误响应结构) **位置**: `pkg/response/response.go` 中的 Response 结构 (已存在) **用途**: 统一的 JSON 错误响应格式,返回给客户端 **字段**: ```go type Response struct { Code int `json:"code"` // 应用错误码 (0 = 成功, >0 = 错误) Data any `json:"data"` // 响应数据 (错误时为 null) Message string `json:"msg"` // 可读消息 (用户友好,已脱敏) Timestamp string `json:"timestamp"` // ISO 8601 时间戳 } ``` **示例**: ```json { "code": 1001, "data": null, "msg": "参数验证失败", "timestamp": "2025-11-14T16:00:00+08:00" } ``` **验证规则**: - Code 必须为非负整数 - Timestamp 必须为 RFC3339 格式 - Message 不能为空 - 错误响应时 Data 为 null **关系**: - 从 AppError 生成 - Request ID 通过响应 Header X-Request-ID 传递,不在响应体中 --- ### 3. ErrorContext (错误上下文) **位置**: 新增 `pkg/errors/context.go` **用途**: 记录错误发生时的请求上下文,用于日志记录和调试 **字段**: ```go type ErrorContext struct { RequestID string // 请求 ID (唯一标识) Method string // HTTP 方法 Path string // 请求路径 Query string // Query 参数 IP string // 客户端 IP UserAgent string // User-Agent UserID string // 用户 ID (如果已认证) Headers map[string]string // 重要的请求头 (可选) StackTrace string // 堆栈跟踪 (panic 时有值) } ``` **方法**: ```go // FromFiberContext 从 Fiber Context 提取错误上下文 func FromFiberContext(c *fiber.Ctx) *ErrorContext // ToLogFields 转换为 Zap 日志字段 func (ec *ErrorContext) ToLogFields() []zap.Field ``` **验证规则**: - RequestID 不能为空 - Method 和 Path 不能为空 - 其他字段可选 **用途场景**: - 记录错误日志时附加完整上下文 - 调试时快速定位问题 - 不返回给客户端 (仅内部使用) --- ### 4. ErrorCode (错误码枚举) **位置**: 新增 `pkg/errors/codes.go` **用途**: 定义所有应用错误码和对应的默认消息 **结构**: ```go const ( // 成功 CodeSuccess = 0 // 客户端错误 (1000-1999) -> 4xx HTTP 状态码 CodeInvalidParam = 1001 // 参数验证失败 CodeMissingToken = 1002 // 缺失认证令牌 CodeInvalidToken = 1003 // 无效或过期的令牌 CodeUnauthorized = 1004 // 未授权 CodeForbidden = 1005 // 禁止访问 CodeNotFound = 1006 // 资源未找到 CodeConflict = 1007 // 资源冲突 CodeTooManyRequests = 1008 // 请求过多 CodeRequestTooLarge = 1009 // 请求体过大 // 服务端错误 (2000-2999) -> 5xx HTTP 状态码 CodeInternalError = 2001 // 内部服务器错误 CodeDatabaseError = 2002 // 数据库错误 CodeRedisError = 2003 // Redis 错误 CodeServiceUnavailable = 2004 // 服务不可用 CodeTimeout = 2005 // 请求超时 CodeTaskQueueError = 2006 // 任务队列错误 ) // 错误消息映射 (中文) var errorMessages = map[int]string{ CodeSuccess: "成功", CodeInvalidParam: "参数验证失败", CodeMissingToken: "缺失认证令牌", CodeInvalidToken: "无效或过期的令牌", CodeUnauthorized: "未授权访问", CodeForbidden: "禁止访问", CodeNotFound: "资源未找到", CodeConflict: "资源冲突", CodeTooManyRequests: "请求过多,请稍后重试", CodeRequestTooLarge: "请求体过大", CodeInternalError: "内部服务器错误", CodeDatabaseError: "数据库错误", CodeRedisError: "缓存服务错误", CodeServiceUnavailable: "服务暂时不可用", CodeTimeout: "请求超时", CodeTaskQueueError: "任务队列错误", } // GetMessage 获取错误消息 func GetMessage(code int, lang string) string ``` **HTTP 状态码映射规则**: ```go func GetHTTPStatus(code int) int { switch code { case CodeInvalidParam, CodeRequestTooLarge: return 400 // Bad Request case CodeMissingToken, CodeInvalidToken, CodeUnauthorized: return 401 // Unauthorized case CodeForbidden: return 403 // Forbidden case CodeNotFound: return 404 // Not Found case CodeConflict: return 409 // Conflict case CodeTooManyRequests: return 429 // Too Many Requests case CodeServiceUnavailable: return 503 // Service Unavailable case CodeTimeout: return 504 // Gateway Timeout default: if code >= 2000 && code < 3000 { return 500 // Internal Server Error } return 400 // 默认客户端错误 } } ``` --- ## 错误处理流程数据流 ``` ┌─────────────┐ │ 请求到达 │ └──────┬──────┘ │ ▼ ┌─────────────────────┐ │ 中间件/Handler │ │ 返回 error │ └──────┬──────────────┘ │ ▼ ┌─────────────────────────────┐ │ Fiber ErrorHandler │ │ 1. 检查响应是否已发送 │ │ 2. 提取错误类型和上下文 │ └──────┬──────────────────────┘ │ ├─────────────┐ │ │ ▼ ▼ ┌────────┐ ┌──────────┐ │AppError│ │其他Error │ └───┬────┘ └────┬─────┘ │ │ └──────┬──────┘ │ ▼ ┌────────────────┐ │ 生成上下文 │ │ ErrorContext │ └────┬───────────┘ │ ├──────────────┐ │ │ ▼ ▼ ┌─────────┐ ┌──────────────┐ │记录日志 │ │生成响应 │ │(完整上下文)│ │ErrorResponse│ └─────────┘ └──────┬───────┘ │ ▼ ┌──────────────┐ │返回给客户端 │ │(脱敏后) │ └──────────────┘ ``` --- ## 常量定义 ### Request ID 上下文键 **位置**: `pkg/constants/constants.go` (已存在,可能需要添加) ```go const ( ContextKeyRequestID = "request_id" // Fiber Locals 中存储 Request ID 的键 HeaderRequestID = "X-Request-ID" // HTTP Header 中的 Request ID 键 ) ``` --- ## 非功能性约束 ### 性能 - ErrorContext 创建: < 0.1ms - 错误日志记录: 异步,不阻塞响应 (< 0.5ms) - 错误响应生成: < 0.5ms - 总错误处理延迟: < 1ms (P95) ### 并发 - AppError 是不可变的 (immutable),线程安全 - ErrorContext 仅在错误处理流程中创建和使用,不共享 - 错误码常量映射只读,无并发问题 ### 内存 - ErrorContext 在请求结束后释放 - 预定义的错误对象可以复用 (如 ErrMissingToken) - 避免在错误处理中分配大量内存 --- ## 与现有代码的集成 ### 现有错误类型 **位置**: `pkg/errors/errors.go` **现状**: ```go var ( ErrMissingToken = errors.New("missing authentication token") ErrInvalidToken = errors.New("invalid or expired token") ErrRedisUnavailable = errors.New("redis unavailable") ErrTooManyRequests = errors.New("too many requests") ) type AppError struct { Code int Message string Err error } ``` **需要的修改**: 1. 为 AppError 添加 HTTPStatus 字段 2. 添加错误码常量 (CodeMissingToken 等) 3. 添加 GetMessage() 函数支持多语言 4. 添加 GetHTTPStatus() 函数映射 HTTP 状态码 ### 现有响应结构 **位置**: `pkg/response/response.go` **现状**: 已有 Response 结构,无需修改 **使用方式**: ```go // 成功响应 (不变) response.Success(c, data) // 错误响应 (现有) response.Error(c, httpStatus, code, message) // 新增: 从 AppError 生成错误响应 response.ErrorFromAppError(c, appErr) ``` --- ## 数据验证 ### 错误码验证 - 必须在定义的范围内 (0, 1000-1999, 2000-2999) - 未定义的错误码记录警告日志 - 默认映射到 500 Internal Server Error ### 错误消息验证 - 不能为空字符串 - 长度限制: 最大 500 字符 - 不包含换行符或特殊字符 (避免日志注入) ### Request ID 验证 - 必须是有效的 UUID v4 格式 - 如果缺失,ErrorHandler 仍然继续处理 - 记录警告日志 --- ## 总结 本数据模型设计: 1. **简洁**: 仅定义必要的运行时结构,无持久化实体 2. **扩展性**: 错误码枚举易于添加新错误类型 3. **安全性**: 错误响应和日志上下文分离,避免敏感信息泄露 4. **性能**: 结构轻量,错误处理开销小 5. **兼容性**: 与现有 pkg/errors 和 pkg/response 自然集成 所有数据结构都遵循 Go 惯用法: 简单的结构体,少量的方法,清晰的职责划分。