You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

16 KiB

错误处理架构说明

📐 架构设计

设计原则

  1. 关注点分离(Separation of Concerns)

    • main.py:应用配置和启动
    • core/error_handlers.py:错误处理逻辑
    • 各层职责清晰,互不干扰
  2. 单一职责原则(Single Responsibility Principle)

    • 每个函数只做一件事
    • 易于测试和维护
  3. 开放封闭原则(Open-Closed Principle)

    • 对扩展开放:可以轻松添加新的错误类型
    • 对修改封闭:不需要修改核心代码
  4. 安全优先(Security First)

    • 不暴露技术细节
    • 环境隔离(开发/生产)
    • 详细日志仅在服务端

🏗️ 架构层次

┌─────────────────────────────────────────────────────────┐
│                     Client (前端)                        │
└────────────────────┬────────────────────────────────────┘
                     │ HTTP Request
                     ↓
┌─────────────────────────────────────────────────────────┐
│                    FastAPI Application                   │
│  ┌───────────────────────────────────────────────────┐  │
│  │              Middleware Stack                      │  │
│  │  • CORS Middleware                                 │  │
│  │  • Logging Middleware                              │  │
│  │  • Request ID Middleware                           │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │              Route Handlers                        │  │
│  │  • /api/v1/projects                                │  │
│  │  • /api/v1/folders                                 │  │
│  │  • ...                                             │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │           Pydantic Validation                      │  │
│  │  • Schema validation                               │  │
│  │  • Type checking                                   │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
│                   ❌ Exception?                          │
│                          ↓                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │     Exception Handlers (error_handlers.py)        │  │
│  │                                                     │  │
│  │  ┌──────────────────────────────────────────────┐ │  │
│  │  │  1. Extract Error Information                │ │  │
│  │  │     • Field path                              │ │  │
│  │  │     • Error type                              │ │  │
│  │  │     • Context                                 │ │  │
│  │  └──────────────────────────────────────────────┘ │  │
│  │                      ↓                              │  │
│  │  ┌──────────────────────────────────────────────┐ │  │
│  │  │  2. Transform to Friendly Message            │ │  │
│  │  │     • Hide technical details                  │ │  │
│  │  │     • Use user-friendly language              │ │  │
│  │  │     • Provide suggestions                     │ │  │
│  │  └──────────────────────────────────────────────┘ │  │
│  │                      ↓                              │  │
│  │  ┌──────────────────────────────────────────────┐ │  │
│  │  │  3. Create Error Response                    │ │  │
│  │  │     • Uniform format                          │ │  │
│  │  │     • Proper status code                      │ │  │
│  │  │     • Timestamp                               │ │  │
│  │  └──────────────────────────────────────────────┘ │  │
│  │                      ↓                              │  │
│  │  ┌──────────────────────────────────────────────┐ │  │
│  │  │  4. Log (Development Only)                   │ │  │
│  │  │     • Detailed error info                     │ │  │
│  │  │     • Request path                            │ │  │
│  │  │     • Stack trace                             │ │  │
│  │  └──────────────────────────────────────────────┘ │  │
│  │                                                     │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
└────────────────────┬────────────────────────────────────┘
                     │ JSON Response
                     ↓
┌─────────────────────────────────────────────────────────┐
│                  Client (前端)                           │
│  • Display error message                                 │
│  • Show field-level errors                               │
│  • User-friendly UI feedback                             │
└─────────────────────────────────────────────────────────┘

📦 模块职责

1. app/main.py - 应用入口

职责

  • 创建 FastAPI 应用实例
  • 配置中间件
  • 注册异常处理器
  • 注册路由
  • 应用生命周期管理

示例

from app.core.error_handlers import register_exception_handlers

app = FastAPI(...)

# 配置中间件
setup_cors(app)
app.add_middleware(LoggingMiddleware)
app.add_middleware(RequestIdMiddleware)

# 注册异常处理器(一行代码)
register_exception_handlers(app)

# 注册路由
app.include_router(api_router, prefix="/api/v1")

2. app/core/error_handlers.py - 错误处理核心

职责

  • 定义错误类型映射
  • 转换技术错误为友好消息
  • 处理各类异常
  • 创建统一响应格式
  • 提供注册函数

核心函数

get_friendly_error_message(error: Dict) -> str

将 Pydantic 验证错误转换为用户友好的消息

# 输入(Pydantic 错误)
{
    "type": "uuid_parsing",
    "msg": "Invalid UUID...",
    "input": "virtual-mine"
}

# 输出(友好消息)
"必须是有效的 UUID 格式,或者不传此字段"

extract_field_path(loc: List) -> str

从错误位置提取字段路径

# 输入
["body", "user", "profile", "email"]

# 输出
"user -> profile -> email"

create_error_response(...) -> JSONResponse

创建统一格式的错误响应

return {
    "success": False,
    "code": 422,
    "message": "参数验证失败",
    "data": {
        "errors": [
            {"field": "folderId", "message": "..."}
        ]
    },
    "timestamp": "2026-02-05T10:00:00Z"
}

validation_exception_handler(...)

处理 Pydantic 验证错误

流程

  1. 遍历所有验证错误
  2. 提取字段路径
  3. 转换为友好消息
  4. 构建错误列表
  5. 开发环境记录详细日志
  6. 返回统一响应

register_exception_handlers(app)

注册所有异常处理器

app.add_exception_handler(404, not_found_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(Exception, general_exception_handler)

3. app/schemas/ - 数据验证

职责

  • 定义 API Schema
  • 使用 Pydantic 进行验证
  • 提供别名映射(snake_case ↔️ camelCase)

示例

class ProjectCreate(BaseModel):
    name: str = Field(..., min_length=1, max_length=255)
    folder_id: Optional[UUID] = Field(None, alias="folderId")
    planned_duration: Optional[int] = Field(None, alias="plannedDuration", gt=0)
    
    class Config:
        populate_by_name = True

🔄 错误处理流程

场景 1: UUID 格式错误

Client Request:
POST /api/v1/projects
{
  "name": "项目",
  "folderId": "virtual-mine"  // ❌ 不是 UUID
}

        ↓

Pydantic Validation:
❌ ValidationError: uuid_parsing

        ↓

validation_exception_handler():
1. Extract: field="folderId", type="uuid_parsing"
2. Transform: "必须是有效的 UUID 格式,或者不传此字段"
3. Create response

        ↓

Client Response:
{
  "success": false,
  "code": 422,
  "message": "folderId: 必须是有效的 UUID 格式,或者不传此字段",
  "data": {
    "errors": [
      {
        "field": "folderId",
        "message": "必须是有效的 UUID 格式,或者不传此字段"
      }
    ]
  }
}

场景 2: 多个字段错误

Client Request:
POST /api/v1/projects
{
  "name": "",              // ❌ 长度不足
  "folderId": "invalid",   // ❌ 不是 UUID
  "plannedDuration": 0     // ❌ 必须大于 0
}

        ↓

Pydantic Validation:
❌ ValidationError: [string_too_short, uuid_parsing, greater_than]

        ↓

validation_exception_handler():
1. Process all 3 errors
2. Transform each error
3. Create error list
4. Build main message: "参数验证失败,共 3 个错误"

        ↓

Client Response:
{
  "success": false,
  "code": 422,
  "message": "参数验证失败,共 3 个错误",
  "data": {
    "errors": [
      {"field": "name", "message": "长度不能少于 1 个字符"},
      {"field": "folderId", "message": "必须是有效的 UUID 格式,或者不传此字段"},
      {"field": "plannedDuration", "message": "必须大于 0,或者不传此字段"}
    ]
  }
}

🔐 安全特性

1. 信息隐藏

隐藏内容

  • Pydantic 错误类型(uuid_parsing, greater_than
  • 框架版本和文档链接
  • 验证规则细节(ctx
  • 内部错误堆栈

保留内容

  • 字段名称
  • 用户友好的错误说明
  • 修复建议

2. 环境隔离

if settings.DEBUG:
    # 开发环境:记录详细日志
    logger.warning(f"验证错误详情: {exc.errors()}")
else:
    # 生产环境:简洁安全
    pass

# 返回给客户端的始终是友好消息
return create_error_response(...)

3. 日志记录

# 服务端日志(不返回给客户端)
logger.error(
    f"未处理的异常 - Path: {request.url.path} - "
    f"错误: {exc}",
    exc_info=True  # 包含完整堆栈信息
)

# 客户端响应(隐藏细节)
return {
    "message": "服务器内部错误"  # 不暴露异常详情
}

🧪 测试策略

1. 单元测试

测试各个函数的功能:

# tests/unit/test_error_handlers.py

def test_extract_field_path():
    assert extract_field_path(["body", "name"]) == "name"

def test_get_friendly_error_message():
    error = {"type": "uuid_parsing"}
    message = get_friendly_error_message(error)
    assert "UUID" in message

2. 集成测试

测试完整的错误处理流程:

# tests/integration/test_project_api.py

async def test_create_project_invalid_uuid():
    response = await client.post(
        "/api/v1/projects",
        json={"name": "test", "folderId": "invalid"}
    )
    assert response.status_code == 422
    data = response.json()
    assert "UUID" in data["message"]

3. 异常处理器测试

测试异常处理器的注册和执行:

def test_register_exception_handlers():
    app = FastAPI()
    register_exception_handlers(app)
    
    assert 404 in app.exception_handlers
    assert HTTPException in app.exception_handlers

📈 扩展指南

添加新的错误类型

  1. ERROR_TYPE_MESSAGES 中添加映射
ERROR_TYPE_MESSAGES = {
    "uuid_parsing": "必须是有效的 UUID 格式,或者不传此字段",
    "email_invalid": "邮箱格式不正确",  # ✅ 新增
}
  1. get_friendly_error_message 中处理特殊逻辑
def get_friendly_error_message(error: Dict[str, Any]) -> str:
    error_type = error.get("type", "")
    
    # 特殊处理
    if error_type == "email_invalid":
        return "请输入有效的邮箱地址,如:user@example.com"
    
    # ... 其他逻辑

添加自定义异常处理器

# 定义自定义异常
class BusinessException(Exception):
    def __init__(self, message: str, code: int = 400):
        self.message = message
        self.code = code

# 添加处理器
async def business_exception_handler(request: Request, exc: BusinessException):
    return create_error_response(
        status_code=exc.code,
        message=exc.message
    )

# 注册
def register_exception_handlers(app):
    # ... 其他处理器 ...
    app.add_exception_handler(BusinessException, business_exception_handler)

📊 性能优化

1. 错误消息缓存

from functools import lru_cache

@lru_cache(maxsize=128)
def get_error_message_template(error_type: str) -> str:
    """缓存常用错误消息模板"""
    return ERROR_TYPE_MESSAGES.get(error_type, "参数验证失败")

2. 延迟日志记录

if settings.DEBUG and logger.isEnabledFor(logging.WARNING):
    # 只在需要时才格式化日志字符串
    logger.warning("验证错误详情: %s", exc.errors())

总结

架构优势

维度 优势
可维护性 代码集中,易于修改
可测试性 独立模块,易于测试
可扩展性 开放封闭,易于扩展
安全性 隐藏细节,保护系统
用户体验 友好提示,清晰易懂
性能 高效处理,低开销

设计模式

  1. 策略模式:不同错误类型使用不同的转换策略
  2. 工厂模式create_error_response 创建统一格式响应
  3. 外观模式register_exception_handlers 简化注册过程
  4. 模板方法模式:统一的错误处理流程

最佳实践

单一职责 依赖注入 环境隔离 安全优先 用户友好 可测试性 可扩展性

这是一个生产级的错误处理架构!