# RESTful API 设计 良好的 API 设计是构建可维护、易用系统的关键。 ## REST 原则 ### 资源命名 ```python # ✅ 好的 URL 设计 GET /users # 获取用户列表 GET /users/123 # 获取特定用户 POST /users # 创建用户 PUT /users/123 # 更新用户(完整替换) PATCH /users/123 # 部分更新用户 DELETE /users/123 # 删除用户 # 嵌套资源 GET /users/123/orders # 用户的订单 GET /users/123/orders/456 # 特定订单 POST /users/123/orders # 创建订单 # ❌ 不好的设计 GET /getUser/123 # 动词放 URL GET /users/123/getOrders # 冗余 POST /users/createUser # 冗余 ``` ### HTTP 方法语义 | 方法 | 用途 | 幂等性 | 安全性 | |------|------|--------|--------| | GET | 获取资源 | 是 | 是 | | POST | 创建资源 | 否 | 否 | | PUT | 完整更新 | 是 | 否 | | PATCH | 部分更新 | 否 | 否 | | DELETE | 删除资源 | 是 | 否 | ### 状态码 ```python from fastapi import HTTPException, status # 成功响应 # 200 OK - 通用成功 # 201 Created - 创建成功 # 204 No Content - 删除成功(无返回内容) @app.post("/users", status_code=status.HTTP_201_CREATED) async def create_user(user: UserCreate): return new_user @app.delete("/users/{id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user(id: int): pass # 客户端错误 # 400 Bad Request - 请求格式错误 # 401 Unauthorized - 未认证 # 403 Forbidden - 无权限 # 404 Not Found - 资源不存在 # 409 Conflict - 冲突(如重复创建) # 422 Unprocessable Entity - 验证失败 # 服务端错误 # 500 Internal Server Error - 服务器错误 # 503 Service Unavailable - 服务不可用 ``` ## 请求与响应设计 ### 统一响应格式 ```python from pydantic import BaseModel from typing import Generic, TypeVar, Optional, List T = TypeVar('T') class Response(BaseModel, Generic[T]): """统一响应格式""" success: bool data: Optional[T] = None error: Optional[str] = None class PagedResponse(BaseModel, Generic[T]): """分页响应""" items: List[T] total: int page: int page_size: int pages: int # 使用示例 @app.get("/users/{id}", response_model=Response[UserResponse]) async def get_user(id: int): user = await get_user_by_id(id) return Response(success=True, data=user) @app.get("/users", response_model=PagedResponse[UserResponse]) async def list_users(page: int = 1, page_size: int = 20): users, total = await get_users_paginated(page, page_size) return PagedResponse( items=users, total=total, page=page, page_size=page_size, pages=(total + page_size - 1) // page_size ) ``` ### 错误响应 ```python from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from pydantic import BaseModel from typing import List, Optional class ErrorDetail(BaseModel): field: Optional[str] = None message: str class ErrorResponse(BaseModel): error: str code: str details: Optional[List[ErrorDetail]] = None # 自定义异常 class APIError(Exception): def __init__(self, message: str, code: str, status_code: int = 400): self.message = message self.code = code self.status_code = status_code # 异常处理器 @app.exception_handler(APIError) async def api_error_handler(request: Request, exc: APIError): return JSONResponse( status_code=exc.status_code, content=ErrorResponse( error=exc.message, code=exc.code ).dict() ) # Pydantic 验证错误处理 from fastapi.exceptions import RequestValidationError @app.exception_handler(RequestValidationError) async def validation_error_handler(request: Request, exc: RequestValidationError): details = [ ErrorDetail(field=".".join(map(str, err["loc"])), message=err["msg"]) for err in exc.errors() ] return JSONResponse( status_code=422, content=ErrorResponse( error="Validation failed", code="VALIDATION_ERROR", details=details ).dict() ) ``` ## 版本控制 ### URL 版本 ```python # 推荐:URL 路径版本 from fastapi import APIRouter v1_router = APIRouter(prefix="/api/v1") v2_router = APIRouter(prefix="/api/v2") @v1_router.get("/users") async def get_users_v1(): return {"version": "v1"} @v2_router.get("/users") async def get_users_v2(): return {"version": "v2", "new_field": "value"} app.include_router(v1_router) app.include_router(v2_router) ``` ### 请求头版本 ```python from fastapi import Header, Depends async def get_api_version( api_version: str = Header("v1", alias="X-API-Version") ): return api_version @app.get("/users") async def get_users(version: str = Depends(get_api_version)): if version == "v2": return {"new_format": True} return {"old_format": True} ``` ## 过滤、排序、分页 ```python from fastapi import Query from typing import List, Optional from enum import Enum class SortOrder(str, Enum): asc = "asc" desc = "desc" @app.get("/users") async def list_users( # 分页 page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), # 过滤 status: Optional[str] = Query(None), created_after: Optional[datetime] = Query(None), search: Optional[str] = Query(None), # 排序 sort_by: str = Query("created_at"), sort_order: SortOrder = Query(SortOrder.desc), ): query = User.query # 应用过滤 if status: query = query.filter(User.status == status) if created_after: query = query.filter(User.created_at >= created_after) if search: query = query.filter( User.name.ilike(f"%{search}%") | User.email.ilike(f"%{search}%") ) # 应用排序 order_col = getattr(User, sort_by) if sort_order == SortOrder.desc: order_col = order_col.desc() query = query.order_by(order_col) # 分页 total = query.count() users = query.offset((page - 1) * page_size).limit(page_size).all() return { "items": users, "total": total, "page": page, "page_size": page_size } ``` ## HATEOAS(超媒体) ```python from pydantic import BaseModel from typing import Dict, List class Link(BaseModel): href: str rel: str method: str = "GET" class UserWithLinks(BaseModel): id: int name: str email: str _links: Dict[str, Link] @app.get("/users/{id}") async def get_user(id: int, request: Request): user = await get_user_by_id(id) base_url = str(request.base_url) return UserWithLinks( **user.dict(), _links={ "self": Link(href=f"{base_url}users/{id}", rel="self"), "orders": Link(href=f"{base_url}users/{id}/orders", rel="orders"), "update": Link(href=f"{base_url}users/{id}", rel="update", method="PUT"), "delete": Link(href=f"{base_url}users/{id}", rel="delete", method="DELETE"), } ) ``` ## API 文档 ### OpenAPI 增强 ```python from fastapi import FastAPI app = FastAPI( title="My API", description=""" ## 功能特性 * **用户管理** - 创建、查询、更新、删除用户 * **订单处理** - 订单相关操作 ## 认证 使用 Bearer Token 认证,在请求头中添加: ``` Authorization: Bearer ``` """, version="1.0.0", contact={ "name": "API Support", "email": "support@example.com" }, license_info={ "name": "MIT" } ) # 为端点添加详细文档 @app.get( "/users/{user_id}", summary="获取用户详情", description="根据用户 ID 获取用户的详细信息", response_description="用户详细信息", responses={ 200: {"description": "成功获取用户"}, 404: {"description": "用户不存在"}, } ) async def get_user(user_id: int): """ 获取指定用户的详细信息: - **user_id**: 用户的唯一标识符 返回用户的完整信息,包括: - 基本信息(姓名、邮箱等) - 创建时间 - 最后更新时间 """ pass ``` ## 最佳实践 ::::{grid} 1 :gutter: 2 :::{grid-item-card} 设计原则 1. **一致性**:URL、响应格式、错误处理保持一致 2. **向后兼容**:使用版本控制,避免破坏性变更 3. **自描述**:良好的命名和文档 4. **无状态**:每个请求包含所需的全部信息 ::: :::{grid-item-card} 安全考虑 1. **始终使用 HTTPS** 2. **验证所有输入** 3. **限制响应数据**(不返回敏感字段) 4. **实施速率限制** 5. **使用适当的认证机制** ::: :::{grid-item-card} 性能优化 1. **支持条件请求**(ETag, Last-Modified) 2. **实现分页** 3. **支持字段选择**(稀疏字段集) 4. **使用缓存** 5. **压缩响应**(gzip) ::: ::::