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
16 KiB
错误处理架构说明
📐 架构设计
设计原则
-
关注点分离(Separation of Concerns)
main.py:应用配置和启动core/error_handlers.py:错误处理逻辑- 各层职责清晰,互不干扰
-
单一职责原则(Single Responsibility Principle)
- 每个函数只做一件事
- 易于测试和维护
-
开放封闭原则(Open-Closed Principle)
- 对扩展开放:可以轻松添加新的错误类型
- 对修改封闭:不需要修改核心代码
-
安全优先(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 验证错误
流程:
- 遍历所有验证错误
- 提取字段路径
- 转换为友好消息
- 构建错误列表
- 开发环境记录详细日志
- 返回统一响应
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
📈 扩展指南
添加新的错误类型
- 在
ERROR_TYPE_MESSAGES中添加映射
ERROR_TYPE_MESSAGES = {
"uuid_parsing": "必须是有效的 UUID 格式,或者不传此字段",
"email_invalid": "邮箱格式不正确", # ✅ 新增
}
- 在
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())
✅ 总结
架构优势
| 维度 | 优势 |
|---|---|
| 可维护性 | 代码集中,易于修改 |
| 可测试性 | 独立模块,易于测试 |
| 可扩展性 | 开放封闭,易于扩展 |
| 安全性 | 隐藏细节,保护系统 |
| 用户体验 | 友好提示,清晰易懂 |
| 性能 | 高效处理,低开销 |
设计模式
- 策略模式:不同错误类型使用不同的转换策略
- 工厂模式:
create_error_response创建统一格式响应 - 外观模式:
register_exception_handlers简化注册过程 - 模板方法模式:统一的错误处理流程
最佳实践
✅ 单一职责 ✅ 依赖注入 ✅ 环境隔离 ✅ 安全优先 ✅ 用户友好 ✅ 可测试性 ✅ 可扩展性
这是一个生产级的错误处理架构!