FastAPI 基础入门

第6步-1-1:FastAPI 框架 - 基础部分

6.1.1 FastAPI 基础入门

简述:FastAPI 是一个现代 Python Web 框架,以高性能、自动文档和类型安全著称。基于 Starlette 和 Uvicorn,支持异步处理,适合快速构建 RESTful API 和微服务。

概念:FastAPI 简介

FastAPI 是一个现代、快速(高性能)的 Python Web 框架,基于 Starlette(路由层)和 Uvicorn(ASGI 服务器)。它支持异步处理、自动生成 OpenAPI 文档、使用 Pydantic 进行数据验证。适合构建微服务、实时应用、高性能 API。

核心特性

  • 快速:基于 Starlette 和 Pydantic,性能与 NodeJS、Go 相当
  • 自动文档:自动生成 Swagger UI 和 ReDoc 交互式 API 文档
  • 类型安全:基于 Python 类型提示,编辑器支持自动补全和类型检查
  • 异步支持:原生支持 async/await,适合高并发场景
  • 数据验证:Pydantic 自动验证请求和响应数据

安装 FastAPI

bash 复制代码
pip install fastapi
pip install uvicorn[standard]  # ASGI 服务器

第一个 FastAPI 应用

python 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello FastAPI"}

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id, "name": "Alice"}

# 运行
# uvicorn main:app --reload

FastAPI 项目结构(推荐)

复制代码
myapi/
├── main.py              # 主应用入口
├── routers/             # 路由模块
│   ├── __init__.py
│   ├── users.py
│   └── items.py
├── schemas/             # Pydantic 模型
│   ├── __init__.py
│   └── user.py
├── models/              # 数据库模型
│   └── user.py
├── crud.py              # 数据库操作
├── database.py          # 数据库配置
└── requirements.txt

启动服务器

bash 复制代码
uvicorn main:app --reload --host 0.0.0.0 --port 8000

访问文档


6.1.2 路由与 HTTP 方法

简述:路由是 URL 路径到处理函数的映射。FastAPI 通过装饰器自动映射 HTTP 方法(GET/POST/PUT/DELETE)到对应的函数,支持路径参数、查询参数和请求体的便捷获取。

概念:路由

路由是 URL 路径到处理函数的映射。FastAPI 通过装饰器自动将函数注册为路由处理器。

GET 请求

python 复制代码
from fastapi import FastAPI, Query, Path
from typing import Optional

app = FastAPI()

# 基础 GET
@app.get("/users")
async def get_users():
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

# 带路径参数
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# 带查询参数
@app.get("/search")
async def search_users(name: Optional[str] = None, age: int = 18):
    return {"name": name, "age": age}

# Query 参数验证
@app.get("/items")
async def get_items(
    skip: int = Query(0, ge=0, description="跳过数量"),
    limit: int = Query(10, ge=1, le=100, description="返回数量")
):
    return {"skip": skip, "limit": limit}

POST 请求

python 复制代码
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI()

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    email: str
    age: Optional[int] = Field(None, ge=0, le=150)
    password: str = Field(..., min_length=6)

class UserResponse(BaseModel):
    id: int
    username: str
    email: str

    class Config:
        from_attributes = True

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    # 实际项目中这里会存入数据库
    new_user = {
        "id": 1,
        "username": user.username,
        "email": user.email
    }
    return new_user

# 使用 Body 接收原始数据
@app.post("/articles")
async def create_article(content: str = Body(..., embed=True)):
    return {"content": content}

PUT 请求

python 复制代码
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

users_db = {1: {"id": 1, "username": "alice", "email": "alice@example.com"}}

class UserUpdate(BaseModel):
    username: str | None = None
    email: str | None = None

@app.put("/users/{user_id}")
async def update_user(user_id: int, user: UserUpdate):
    if user_id not in users_db:
        raise HTTPException(status_code=404, detail="用户不存在")

    # 更新非空字段
    if user.username is not None:
        users_db[user_id]["username"] = user.username
    if user.email is not None:
        users_db[user_id]["email"] = user.email

    return users_db[user_id]

DELETE 请求

python 复制代码
@app.delete("/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
    if user_id not in users_db:
        raise HTTPException(status_code=404, detail="用户不存在")

    del users_db[user_id]
    return None

@app.delete("/users")
async def delete_all_users():
    users_db.clear()
    return {"message": "所有用户已删除"}

请求参数类型汇总

参数位置 获取方式 示例
路径参数 函数参数 @app.get("/users/{id}")
查询参数 Query() name: str = Query(None)
请求体 Body() / Pydantic user: UserCreate
表单数据 Form() username: str = Form(...)
请求头 Header() x_token: str = Header(None)
Cookie Cookie() session_id: str = Cookie(None)

Form Data(表单数据)

使用 Form() 接收 application/x-www-form-urlencodedmultipart/form-data 格式的表单数据,适用于传统 HTML 表单提交和 OAuth2 密码模式登录场景。注意:FormBody(JSON)不能在同一个路由中同时使用。

python 复制代码
from fastapi import FastAPI, Form, UploadFile, File

app = FastAPI()

@app.post("/login")
async def login(
    username: str = Form(..., min_length=3, max_length=50),
    password: str = Form(..., min_length=6),
    remember: bool = Form(False)
):
    return {"username": username, "remember": remember}

@app.post("/profile")
async def update_profile(
    name: str = Form(...),
    bio: str = Form(None),
    avatar: UploadFile = File(None)
):
    return {"name": name, "bio": bio, "avatar": avatar.filename if avatar else None}

提示 :当路由参数中使用了 Form()File(),FastAPI 会自动将请求的 Content-Type 设为 multipart/form-dataapplication/x-www-form-urlencoded,而不是 JSON。如果需要接收 JSON 请求体,应使用 Pydantic 模型。

使用 Cookie() 读取请求中的 Cookie,通过 Response.set_cookie() 写入 Cookie。常用于会话管理、主题偏好、Token 刷新等场景。

python 复制代码
from fastapi import FastAPI, Cookie, Response
from typing import Optional

app = FastAPI()

@app.get("/set-cookie")
async def set_cookie(response: Response):
    response.set_cookie(
        key="session_id",
        value="abc123",
        max_age=3600,
        httponly=True,
        samesite="lax",
        secure=False
    )
    response.set_cookie(
        key="theme",
        value="dark",
        max_age=86400 * 30
    )
    return {"message": "Cookie 已设置"}

@app.get("/get-cookie")
async def get_cookie(
    session_id: Optional[str] = Cookie(None),
    theme: Optional[str] = Cookie(None, alias="theme")
):
    return {
        "session_id": session_id,
        "theme": theme
    }

@app.get("/delete-cookie")
async def delete_cookie(response: Response):
    response.delete_cookie("session_id")
    return {"message": "Cookie 已删除"}

Cookie 安全参数httponly=True 防止 JavaScript 访问(防 XSS),secure=True 仅 HTTPS 传输,samesite="lax" 防止 CSRF 攻击。生产环境建议三个参数都启用。

Header 操作

使用 Header() 获取请求头信息。FastAPI 会自动将请求头名称中的连字符 - 转换为下划线 _(如 X-Request-IDx_request_id),并统一转为小写。

python 复制代码
from fastapi import FastAPI, Header
from typing import Optional

app = FastAPI()

@app.get("/headers")
async def read_headers(
    user_agent: Optional[str] = Header(None),
    accept_language: Optional[str] = Header(None),
    x_request_id: Optional[str] = Header(None, alias="X-Request-ID")
):
    return {
        "user_agent": user_agent,
        "accept_language": accept_language,
        "x_request_id": x_request_id
    }

@app.get("/client-info")
async def client_info(
    x_forwarded_for: Optional[str] = Header(None),
    x_real_ip: Optional[str] = Header(None)
):
    client_ip = x_real_ip or (x_forwarded_for.split(",")[0] if x_forwarded_for else "unknown")
    return {"client_ip": client_ip}

提示Header() 参数名中的下划线会自动映射到请求头的连字符。例如 x_api_key: str = Header(...) 会读取 X-Api-Key 请求头。也可以用 alias 参数显式指定请求头名称。

Request 对象

通过 request: Request 直接访问底层 Starlette 请求对象,获取请求头、查询参数、Cookie、客户端 IP、请求体等完整信息。适用于需要灵活处理请求的场景。

python 复制代码
from fastapi import FastAPI, Request
from typing import Optional

app = FastAPI()

@app.get("/request-info")
async def request_info(request: Request):
    return {
        "method": request.method,
        "url": str(request.url),
        "path": request.url.path,
        "query_params": dict(request.query_params),
        "client_host": request.client.host if request.client else None,
        "client_port": request.client.port if request.client else None,
        "headers": dict(request.headers),
        "cookies": request.cookies,
        "user_agent": request.headers.get("user-agent"),
    }

@app.post("/raw-body")
async def read_raw_body(request: Request):
    content_type = request.headers.get("content-type")
    if "json" in content_type:
        data = await request.json()
    elif "form" in content_type:
        data = dict(await request.form())
    else:
        data = (await request.body()).decode()
    return {"content_type": content_type, "data": data}

@app.get("/client-ip")
async def get_client_ip(request: Request):
    forwarded = request.headers.get("x-forwarded-for")
    real_ip = request.headers.get("x-real-ip")
    client_ip = real_ip or (forwarded.split(",")[0] if forwarded else request.client.host)
    return {"client_ip": client_ip}

提示Request 对象是 Starlette 提供的,包含完整的 HTTP 请求信息。在中间件、依赖注入和路由中都可以使用。当 FastAPI 的声明式参数(QueryHeaderCookie 等)无法满足需求时,可以直接使用 Request 对象。

模块化路由:创建独立 Router 文件

概念 :当项目规模增大时,应将不同模块的路由拆分到独立的文件中。使用 APIRouter 可以创建模块化的路由,注册到主应用时通过 app.include_router() 统一管理。

项目目录结构
复制代码
myapi/
├── main.py              # 主应用入口
├── routers/             # 路由模块目录
│   ├── __init__.py      # 空文件,使 routers 成为包
│   ├── users.py         # 用户相关路由
│   ├── items.py         # 商品相关路由
│   └── orders.py        # 订单相关路由
├── schemas/             # Pydantic 模型
│   ├── __init__.py
│   ├── user.py
│   └── item.py
└── requirements.txt
创建 users.py 路由文件
python 复制代码
# routers/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional

router = APIRouter(prefix="/users", tags=["用户管理"])

# 模拟数据库
users_db = [
    {"id": 1, "username": "alice", "email": "alice@example.com"},
    {"id": 2, "username": "bob", "email": "bob@example.com"}
]

# Pydantic 模型
class UserCreate(BaseModel):
    username: str
    email: str

class UserUpdate(BaseModel):
    username: Optional[str] = None
    email: Optional[str] = None

class UserResponse(BaseModel):
    id: int
    username: str
    email: str

# GET 查询所有用户
@router.get("/", response_model=List[UserResponse])
async def get_users(skip: int = 0, limit: int = 10):
    return users_db[skip:skip + limit]

# GET 查询单个用户
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = next((u for u in users_db if u["id"] == user_id), None)
    if not user:
        raise HTTPException(status_code=404, detail="用户不存在")
    return user

# POST 创建用户
@router.post("/", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    new_id = max(u["id"] for u in users_db) + 1 if users_db else 1
    new_user = {"id": new_id, **user.model_dump()}
    users_db.append(new_user)
    return new_user

# PUT 更新用户
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user: UserUpdate):
    for u in users_db:
        if u["id"] == user_id:
            if user.username is not None:
                u["username"] = user.username
            if user.email is not None:
                u["email"] = user.email
            return u
    raise HTTPException(status_code=404, detail="用户不存在")

# DELETE 删除用户
@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
    global users_db
    for i, u in enumerate(users_db):
        if u["id"] == user_id:
            users_db.pop(i)
            return None
    raise HTTPException(status_code=404, detail="用户不存在")
创建 items.py 路由文件
python 复制代码
# routers/items.py
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional

router = APIRouter(prefix="/items", tags=["商品管理"])

items_db = [
    {"id": 1, "name": "Laptop", "price": 2999.99, "stock": 50},
    {"id": 2, "name": "Phone", "price": 999.99, "stock": 100}
]

class ItemCreate(BaseModel):
    name: str
    price: float
    stock: int = 0

class ItemUpdate(BaseModel):
    name: Optional[str] = None
    price: Optional[float] = None
    stock: Optional[int] = None

class ItemResponse(BaseModel):
    id: int
    name: str
    price: float
    stock: int

@router.get("/", response_model=List[ItemResponse])
async def get_items():
    return items_db

@router.get("/{item_id}", response_model=ItemResponse)
async def get_item(item_id: int):
    item = next((i for i in items_db if i["id"] == item_id), None)
    if not item:
        raise HTTPException(status_code=404, detail="商品不存在")
    return item

@router.post("/", response_model=ItemResponse, status_code=201)
async def create_item(item: ItemCreate):
    new_id = max(i["id"] for i in items_db) + 1 if items_db else 1
    new_item = {"id": new_id, **item.model_dump()}
    items_db.append(new_item)
    return new_item

@router.delete("/{item_id}", status_code=204)
async def delete_item(item_id: int):
    global items_db
    for i, item in enumerate(items_db):
        if item["id"] == item_id:
            items_db.pop(i)
            return None
    raise HTTPException(status_code=404, detail="商品不存在")
创建 main.py 主应用
python 复制代码
# main.py
from fastapi import FastAPI
from routers import users, items

app = FastAPI(
    title="MyAPI",
    description="模块化 FastAPI 应用示例",
    version="1.0.0"
)

# 注册路由
app.include_router(users.router)
app.include_router(items.router)

@app.get("/")
async def root():
    return {"message": "欢迎访问 MyAPI", "docs": "/docs"}

@app.get("/health")
async def health_check():
    return {"status": "healthy"}
运行和测试
bash 复制代码
# 启动服务器
uvicorn main:app --reload

# 访问文档
# http://127.0.0.1:8000/docs

# API 路径结构
# GET  /users/          - 查询用户列表
# POST /users/          - 创建用户
# GET  /users/{user_id} - 查询单个用户
# PUT  /users/{user_id} - 更新用户
# DELETE /users/{user_id} - 删除用户

# GET  /items/          - 查询商品列表
# POST /items/          - 创建商品
# GET  /items/{item_id} - 查询单个商品
# DELETE /items/{item_id} - 删除商品
routers/init.py 文件
python 复制代码
# routers/__init__.py
from . import users, items

__all__ = ["users", "items"]
使用子路由前缀和标签分组
python 复制代码
# 如果需要更细粒度的控制,可以单独指定参数
app.include_router(
    users.router,
    prefix="/api/v1",      # 添加路径前缀
    tags=["V1 - 用户"],    # 自定义标签
    dependencies=[]        # 全局依赖(如认证)
)

# 访问路径变为:/api/v1/users/
在路由文件中使用依赖注入
python 复制代码
# routers/users.py
from fastapi import Depends, HTTPException
from typing import Generator

# 模拟数据库会话依赖
def get_db():
    db = {"connected": True}
    yield db
    # 清理资源

# 在路由中使用依赖
@router.get("/{user_id}")
async def get_user(user_id: int, db: dict = Depends(get_db)):
    print(f"数据库连接: {db}")
    # ... 查询逻辑
    pass

# 使用认证依赖
async def verify_token(token: str):
    if token != "valid-token":
        raise HTTPException(status_code=401, detail="无效的令牌")
    return {"user_id": 1}

@router.get("/profile")
async def get_user_profile(token: str = Depends(verify_token)):
    return {"user_id": token["user_id"], "username": "alice"}

6.1.3 Pydantic 数据验证

简述:Pydantic 是 Python 数据验证库,FastAPI 使用它进行请求数据的自动验证和响应数据的序列化。通过定义数据模型,可以实现字段类型检查、默认值设置和自定义验证规则。

概念:Pydantic

Pydantic 是 Python 数据验证库,FastAPI 使用它进行请求数据验证和响应数据序列化。定义模型后,FastAPI 自动验证输入数据并生成 JSON Schema。Pydantic 底层调用 json.dumps(),但自动处理了 datetime、UUID、Enum 等特殊类型的转换。

基础模型

python 复制代码
from pydantic import BaseModel, Field, EmailStr, ConfigDict
from typing import Optional, List
from datetime import datetime

class User(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool = True

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    email: EmailStr  # 自动验证邮箱格式
    password: str = Field(..., min_length=6)

class UserUpdate(BaseModel):
    username: Optional[str] = Field(None, min_length=3, max_length=50)
    email: Optional[EmailStr] = None
    password: Optional[str] = Field(None, min_length=6)

嵌套模型

python 复制代码
class Address(BaseModel):
    street: str
    city: str
    country: str
    zip_code: str

class OrderItem(BaseModel):
    product_id: int
    quantity: int = Field(..., gt=0, description="数量必须大于0")
    price: float = Field(..., gt=0)

class Order(BaseModel):
    id: int
    user_id: int
    items: List[OrderItem]
    address: Address
    total_amount: float
    created_at: datetime
    status: str = "pending"

    class Config:
        from_attributes = True

嵌套模型请求示例

python 复制代码
@app.post("/orders")
async def create_order(order: Order):
    # order.items 是 OrderItem 对象列表
    # order.address 是 Address 对象
    total = sum(item.quantity * item.price for item in order.items)
    return {"order_id": 1, "total": total}

Pydantic 字段验证器

python 复制代码
from pydantic import field_validator
from typing import Annotated

class UserRegister(BaseModel):
    username: str
    password: str
    password_confirm: str
    age: int

    @field_validator("username")
    @classmethod
    def username_alphanumeric(cls, v):
        if not v.replace("_", "").isalnum():
            raise ValueError("用户名只能包含字母、数字、下划线")
        return v

    @field_validator("password_confirm")
    @classmethod
    def passwords_match(cls, v, info):
        # 注意:需要从 info.fields 获取其他字段值
        if 'password' in info.data and v != info.data['password']:
            raise ValueError("两次密码输入不一致")
        return v

    @field_validator("age")
    @classmethod
    def age_valid(cls, v):
        if v < 0 or v > 150:
            raise ValueError("年龄必须在 0-150 之间")
        return v

高级验证:model_validator(Pydantic V2)

python 复制代码
from pydantic import model_validator

class UserLogin(BaseModel):
    username: str
    password: str
    code: str

    @model_validator(mode='after')
    def validate_code(self):
        if self.code != "1234":
            raise ValueError("验证码错误")
        return self

提示 :Pydantic V2 中 root_validator 已被 model_validator 替代。mode='after' 表示在所有字段验证完成后执行,此时可通过 self.field 访问已验证的字段值。

可选字段与默认值

python 复制代码
from pydantic import BaseModel, ConfigDict
from typing import Optional, List

class Product(BaseModel):
    name: str
    price: float = 0.0
    description: Optional[str] = None
    tags: List[str] = []
    is_featured: bool = False

    model_config = ConfigDict(
        str_strip_whitespace=True,
        populate_by_name=True
    )

提示 :Pydantic V2 中使用 model_config = ConfigDict(...) 替代了 V1 的 class Config 写法。str_strip_whitespace=True 会自动去除字符串首尾空格,populate_by_name=True 允许通过字段别名填充数据。

字段别名与序列化高级用法

使用 alias 实现字段名映射(前后端字段名不一致时)、serialization_alias 区分输入输出别名、model_dump() 系列方法控制序列化输出,是实际项目中非常实用的功能。

python 复制代码
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from datetime import datetime

class UserCreate(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    username: str = Field(..., alias="userName", min_length=3, max_length=50)
    email: str = Field(..., alias="emailAddress")
    phone_number: Optional[str] = Field(None, alias="phone", serialization_alias="phoneNumber")

data = {"userName": "alice", "emailAddress": "alice@example.com", "phone": "13800138000"}
user = UserCreate.model_validate(data)

print(user.username)
print(user.model_dump())
print(user.model_dump(by_alias=True))
print(user.model_dump(by_alias=True, exclude_unset=True))
print(user.model_dump_json(by_alias=True))

输出

复制代码
alice
{'username': 'alice', 'email': 'alice@example.com', 'phone_number': '13800138000'}
{'userName': 'alice', 'emailAddress': 'alice@example.com', 'phone': '13800138000'}
{'userName': 'alice', 'emailAddress': 'alice@example.com', 'phoneNumber': '13800138000'}
{"userName":"alice","emailAddress":"alice@example.com","phoneNumber":"13800138000"}

关键方法对比

方法 用途 返回类型
model_dump() 序列化为字典 dict
model_dump_json() 序列化为 JSON 字符串 str
model_validate(data) 从字典/JSON 创建模型 模型实例

关键参数对比

参数 说明
by_alias=True 使用别名而非字段名输出
exclude_unset=True 仅输出用户显式设置的字段(未设置的不输出)
exclude_none=True 排除值为 None 的字段
exclude={"field"} 排除指定字段
include={"field"} 仅包含指定字段
python 复制代码
class Article(BaseModel):
    title: str
    content: str
    view_count: int = 0
    tags: List[str] = []
    is_draft: bool = True

article = Article(title="FastAPI", content="Hello")

article.model_dump()
article.model_dump(exclude_unset=True)
article.model_dump(exclude={"content", "is_draft"})
article.model_dump(include={"title", "content"})
article.model_dump(exclude_none=True)

提示exclude_unset=True 在 PATCH 更新接口中特别有用------只更新用户提交的字段,未提交的字段保持原值。serialization_alias 是 Pydantic V2 新增功能,允许输入用 alias 解析,输出用 serialization_alias 序列化,实现输入输出字段名分离。


6.1.4 响应与状态码

简述:FastAPI 允许为每个端点定义响应模型,控制返回数据的结构和格式。同时支持多种 HTTP 状态码,可为不同场景定义不同的响应类型和内容。

概念:响应模型

FastAPI 允许为每个端点定义响应模型,用于验证和序列化返回数据。可以定义多个响应状态码及其对应的响应模型。

定义响应模型

python 复制代码
from fastapi import FastAPI, Response, status
from fastapi.responses import JSONResponse, RedirectResponse
from pydantic import BaseModel
from typing import Optional, List

app = FastAPI()

class Item(BaseModel):
    id: int
    name: str
    description: Optional[str] = None
    price: float

class ErrorResponse(BaseModel):
    error: str
    detail: str

# 使用 response_model
@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
    return {"id": item_id, "name": "Phone", "price": 999.99}

# 返回列表
@app.get("/items", response_model=List[Item])
async def list_items():
    return [
        {"id": 1, "name": "Phone", "price": 999.99},
        {"id": 2, "name": "Laptop", "price": 2999.99}
    ]

# 排除字段
class User(BaseModel):
    id: int
    username: str
    email: str
    password: str  # 不应返回给客户端

class UserPublic(BaseModel):
    id: int
    username: str

@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
    return {"id": user_id, "username": "alice", "email": "alice@example.com", "password": "secret"}

状态码

python 复制代码
from fastapi import status

@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
    return {"id": 1, **item.model_dump()}

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    return None

@app.get("/error", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
async def error():
    return {"error": "服务器错误"}

多种响应

python 复制代码
from fastapi import FastAPI, HTTPException
from typing import Union

@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        200: {"model": Item, "description": "成功"},
        404: {"model": ErrorResponse, "description": "未找到"},
        500: {"description": "服务器错误"}
    }
)
async def get_item(item_id: int):
    if item_id == 0:
        raise HTTPException(status_code=404, detail="Item not found")
    if item_id < 0:
        return Response(status_code=500)
    return {"id": item_id, "name": "Phone", "price": 999.99}

JSONResponse

python 复制代码
from fastapi.responses import JSONResponse

@app.get("/custom-response")
async def custom_response():
    return JSONResponse(
        content={"message": "Hello", "data": [1, 2, 3]},
        status_code=200,
        headers={"X-Custom-Header": "value"}
    )

@app.get("/redirect")
async def redirect():
    return RedirectResponse(url="/", status_code=302)

响应模型高级用法

使用 response_model_exclude_unsetresponse_model_include/excluderesponse_model_by_alias 精细控制响应数据的输出,解决实际项目中字段过滤、部分更新、别名输出等需求。

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI()

class UserFull(BaseModel):
    id: int
    username: str
    email: str
    hashed_password: str
    is_active: bool = True
    is_superuser: bool = False
    full_name: Optional[str] = None

    model_config = {"from_attributes": True}

class UserPublic(BaseModel):
    id: int
    username: str
    email: str

    model_config = {"from_attributes": True}

# 方式1:使用不同的响应模型过滤字段
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user_public(user_id: int):
    return {"id": user_id, "username": "alice", "email": "a@b.com", "hashed_password": "xxx"}

# 方式2:response_model_exclude_unset - 仅返回用户显式设置的字段
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def get_item(item_id: int):
    return {"id": item_id, "name": "Phone", "price": 999.99}

# 方式3:response_model_exclude - 排除敏感字段
@app.get("/users-full/{user_id}", response_model=UserFull, response_model_exclude={"hashed_password"})
async def get_user_safe(user_id: int):
    return {
        "id": user_id, "username": "alice", "email": "a@b.com",
        "hashed_password": "secret", "is_active": True
    }

# 方式4:response_model_include - 仅包含指定字段
@app.get("/users-brief/{user_id}", response_model=UserFull, response_model_include={"id", "username"})
async def get_user_brief(user_id: int):
    return {
        "id": user_id, "username": "alice", "email": "a@b.com",
        "hashed_password": "secret"
    }

# 方式5:response_model_by_alias - 使用别名输出
@app.get("/users-alias/{user_id}", response_model=UserFull, response_model_by_alias=True)
async def get_user_alias(user_id: int):
    return {"id": user_id, "username": "alice", "email": "a@b.com", "hashed_password": "xxx"}

响应模型参数对比

参数 说明 典型场景
response_model 指定响应模型类 所有接口
response_model_exclude_unset=True 仅输出已设置的字段 GET 接口,避免输出默认值
response_model_exclude={"field"} 排除指定字段 排除密码等敏感字段
response_model_include={"field"} 仅包含指定字段 简要信息接口
response_model_by_alias=True 使用别名输出 前端要求驼峰命名

提示 :推荐优先使用不同的响应模型(如 UserPublic vs UserFull),比 exclude/include 更清晰、更易维护。exclude_unset 在 PATCH 接口中配合 model_dump(exclude_unset=True) 使用效果最佳。


6.1.5 依赖注入系统

简述:依赖注入是一种设计模式,FastAPI 提供了强大的声明式依赖注入系统。可以将数据库连接、认证、配置等逻辑提取为可复用的依赖项,在路由函数中按需使用。

概念:依赖注入

FastAPI 的依赖注入系统允许声明式地共享逻辑、数据连接、认证等。依赖项可以是函数、类或可调用对象,FastAPI 自动解析参数并调用。

简单依赖

python 复制代码
from fastapi import Depends, FastAPI

app = FastAPI()

# 定义依赖
def get_query_param(q: str = "default"):
    return q

@app.get("/items")
async def read_items(q: str = Depends(get_query_param)):
    return {"query": q}

# 类形式的依赖
class QueryParams:
    def __init__(self, q: str = "default", skip: int = 0, limit: int = 10):
        self.q = q
        self.skip = skip
        self.limit = limit

@app.get("/search")
async def search(params: QueryParams = Depends()):
    return {"q": params.q, "skip": params.skip, "limit": params.limit}

数据库依赖

下面案例中说明的 FastAPI 的依赖在数据库操作的用法,包括连接数据库、查询数据、创建数据等。注意执行顺序:

python 复制代码
# 模拟数据库连接
def connect_to_database():
    print("1、连接数据库...")
    return {"conn": "mysql_connection", "status": "connected"}

def get_db():
    db = connect_to_database()
    try:
        yield db  # 将 db 注入到路由函数
    finally:
        print("4、关闭数据库连接...")  # 请求结束后自动执行

# ========== 在路由中使用 ==========
@app.get("/users")
def get_users(db: dict = Depends(get_db)):
    print(f"查询用户,使用: {db['conn']}")
    return [{"id": 1, "name": "Alice"}]

@app.post("/users")
def create_user(db: dict = Depends(get_db)):
    print(f"2、创建用户,使用: {db['conn']}")
    print(f"3、创建用户完成")
    return {"id": 2, "name": "Bob"}

配置依赖

python 复制代码
from pydantic import BaseModel

class Settings(BaseModel):
    app_name: str = "FastAPI App"
    debug: bool = False
    database_url: str

settings = Settings(app_name="My App", debug=True, database_url="sqlite:///./db.sqlite")

@app.get("/settings")
async def get_settings(current_settings: Settings = Depends(lambda: settings)):
    return current_settings.model_dump()

依赖链

python 复制代码
# 依赖链示例
def get_db():
    return {"connection": "db_connection"}

def get_current_user(db: dict = Depends(get_db)):
    return {"user_id": 1, "username": "alice"}

def verify_admin(user: dict = Depends(get_current_user)):
    if user.get("role") != "admin":
        raise HTTPException(status_code=403, detail="需要管理员权限")
    return user

@app.get("/admin/dashboard")
async def admin_dashboard(admin: dict = Depends(verify_admin)):
    return {"message": "欢迎管理员"}

可选依赖

python 复制代码
from typing import Optional

def get_optional_db():
    return {"db": "connected"}

@app.get("/optional")
async def optional_dep(db: Optional[dict] = Depends(get_optional_db)):
    if db:
        return {"status": "connected"}
    return {"status": "not_connected"}

循环依赖问题

FastAPI 循环依赖问题:当依赖项之间存在循环引用时,FastAPI 会报错。解决方法是避免循环依赖,或者使用 Depends 注解指定依赖项的顺序。


6.1.6 中间件

简述:中间件是在请求处理前后执行的函数,可以修改请求或响应、添加日志、处理 CORS、计算响应时间等。FastAPI 支持内置中间件和自定义中间件,按添加顺序依次执行。

概念:中间件

中间件是在请求处理前后执行的函数,可以修改请求或响应、记录日志、添加性能计时等。

内置中间件

python 复制代码
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
import time

app = FastAPI()

# CORS 中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # 允许的源
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

# GZip 压缩中间件
app.add_middleware(GZipMiddleware, minimum_size=1000)

# 自定义中间件
@app.middleware("http")
async def add_process_time_header(request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

@app.middleware("http")
async def log_requests(request, call_next):
    print(f"请求: {request.method} {request.url.path}")
    response = await call_next(request)
    print(f"响应: {response.status_code}")
    return response

CORS 详解

python 复制代码
# 详细 CORS 配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://localhost:8080",
        "https://myapp.com"
    ],
    allow_origin_regex="https://.*\.myapp\.com",  # 正则匹配
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
    expose_headers=["X-Custom-Header"],
    max_age=600  # 预检请求缓存时间(秒)
)

中间件顺序

FastAPI 的中间件执行顺序是后进先出(LIFO),也就是后定义的中间件先执行请求,后定义的后响应。

python 复制代码
app = FastAPI()

# 中间件按添加顺序执行
# 顺序: TrustedHost -> GZip -> CORS -> 自定义 -> 路由

app.add_middleware(GZipMiddleware, minimum_size=500)
app.add_middleware(CORSMiddleware, allow_origins=["*"])

@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件执行 1. 请求前")
    response = await call_next(request)
    print("中间件执行 1. 响应后 - 在这里执行后续操作")
    return response

@app.middleware("http")
async def middleware2(request, call_next):
    print("中间件执行 2. 请求前")
    response = await call_next(request)
    print("中间件执行 2. 响应后 - 在这里执行后续操作")
    return response


@app.get("/")
async def root():
    print("业务逻辑在中间执行")
    return {"message": "Hello"}

调用 root 接口最后输出:

复制代码
中间件执行 2. 请求前
中间件执行 1. 请求前
业务逻辑在中间执行
中间件执行 1. 响应后 - 在这里执行后续操作
中间件执行 2. 响应后 - 在这里执行后续操作

6.1.7 错误处理与异常

简述:FastAPI 提供了 HTTPException 用于抛出 HTTP 错误响应,同时支持自定义异常处理器和全局异常处理。可以统一处理业务异常、验证错误和服务器错误,提升 API 的健壮性。

概念:HTTPException

HTTPException 用于抛出 HTTP 错误响应,可以指定状态码和错误信息。

HTTPException

python 复制代码
from fastapi import HTTPException, status

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    if user_id < 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="用户ID不能为负数",
            headers={"X-Error": "invalid_id"}
        )
    if user_id > 1000:
        raise HTTPException(status_code=404, detail="用户不存在")
    return {"user_id": user_id}

自定义异常处理器

概念 :自定义异常处理器用于捕获和处理项目中定义的业务异常。通过 @app.exception_handler() 装饰器注册,将特定异常类型映射到统一的响应格式,适合统一错误码和错误结构的场景。

python 复制代码
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse

app = FastAPI()

class CustomException(Exception):
    def __init__(self, name: str, message: str):
        self.name = name
        self.message = message

@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content={
            "error": exc.name,
            "message": exc.message,
            "path": str(request.url)
        }
    )

@app.get("/custom-error")
async def raise_custom_error():
    raise CustomException("BusinessError", "业务逻辑错误")

全局异常处理器

概念 :全局异常处理器使用 Exception 作为异常类型,捕获所有未被特定处理器处理的异常。通常放在最后注册,作为"兜底"处理器,用于记录日志、返回友好的错误信息。生产环境中不应暴露详细的错误堆栈。

python 复制代码
from fastapi.responses import JSONResponse
import traceback

@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    # 记录日志
    print(f"全局异常: {exc}")
    traceback.print_exc()

    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "message": "服务器内部错误",
            "detail": str(exc) if app.debug else None
        }
    )

ValidationError 处理

概念RequestValidationError 是 FastAPI 自动抛出的验证异常,当请求数据不符合 Pydantic 模型定义时触发。自定义处理器可以将验证错误格式化为更友好的响应,指明哪个字段出错及错误原因。

python 复制代码
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    errors = []
    for error in exc.errors():
        errors.append({
            "field": ".".join(str(loc) for loc in error["loc"]),
            "message": error["msg"],
            "type": error["type"]
        })

    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={
            "message": "数据验证失败",
            "errors": errors
        }
    )

6.1.8 swagger文档配置

简述:FastAPI 允许为每个路由配置额外的元数据,如标签、摘要、描述、状态码等,这些信息会显示在自动生成的 API 文档中。使用 APIRouter 可以模块化管理大量路由。

概念:路径操作配置

FastAPI 允许为每个路由配置额外的元数据,如标签、摘要、描述、状态码等,这些信息会显示在自动生成的 API 文档中。

路径操作装饰器参数

python 复制代码
from fastapi import FastAPI, status
from typing import List

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tags: List[str] = []

@app.post(
    "/items",
    response_model=Item,
    status_code=status.HTTP_201_CREATED,
    tags=["items"],
    summary="创建商品",
    description="创建一个新的商品,支持多标签",
    responses={
        201: {"description": "商品创建成功"},
        400: {"description": "请求参数错误"},
        401: {"description": "未授权"}
    },
    deprecated=False
)
async def create_item(item: Item):
    return item

@app.get(
    "/items/{item_id}",
    tags=["items"],
    summary="获取商品详情",
    description="根据ID获取商品详细信息",
    response_description="商品详情信息"
)
async def get_item(item_id: int):
    return {"name": "Phone", "price": 999.99}

# 使用 deprecated 标记
@app.get("/old-items", tags=["items"], deprecated=True)
async def get_old_items():
    return [{"id": 1}]

APIRouter 模块化

python 复制代码
from fastapi import APIRouter, Depends, HTTPException
from typing import List

router = APIRouter(prefix="/users", tags=["用户管理"])
admin_router = APIRouter(prefix="/admin", tags=["管理后台"])

# 用户相关路由
@router.get("/", response_model=List[dict])
async def list_users(skip: int = 0, limit: int = 10):
    return [{"id": i} for i in range(skip, skip + limit)]

@router.get("/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id, "name": "Alice"}

@router.post("/", status_code=201)
async def create_user(name: str):
    return {"id": 1, "name": name}

# 管理员路由
@admin_router.get("/stats")
async def get_stats():
    return {"total_users": 100}

# 注册路由
app = FastAPI()
app.include_router(router)
app.include_router(admin_router)

# 访问: GET /users/
# 访问: GET /users/{user_id}
# 访问: POST /users/
# 访问: GET /admin/stats

6.1.9 Background Tasks(后台任务)

简述 :Background Tasks 允许在响应返回后执行耗时操作(如发送邮件、日志记录、数据处理),不阻塞 API 响应。FastAPI 内置 BackgroundTasks 类,无需额外依赖即可使用。

概念:后台任务

后台任务是在 HTTP 响应返回给客户端之后才执行的函数。适用于不需要即时返回结果的操作,如发送通知邮件、写入日志、更新缓存等。与 Celery 等分布式任务队列相比,Background Tasks 更轻量,适合简单场景。

基础用法

在路由函数中注入 BackgroundTasks,通过 add_task() 添加后台任务,支持传参。

python 复制代码
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel, EmailStr

app = FastAPI()

class MessageCreate(BaseModel):
    email: EmailStr
    subject: str
    content: str

def send_email(email: str, subject: str, content: str):
    import time
    time.sleep(3)
    print(f"邮件已发送: {email}, 主题: {subject}")

def write_log(message: str):
    with open("app.log", "a", encoding="utf-8") as f:
        f.write(f"{message}\n")

@app.post("/messages")
async def create_message(
    message: MessageCreate,
    background_tasks: BackgroundTasks
):
    background_tasks.add_task(send_email, message.email, message.subject, message.content)
    background_tasks.add_task(write_log, f"新消息: {message.subject}")

    return {"message": "消息已接收,邮件将在后台发送"}

依赖注入中使用后台任务

在依赖函数中添加后台任务,路由函数无需直接操作 BackgroundTasks

python 复制代码
from fastapi import Depends, BackgroundTasks

def log_request(background_tasks: BackgroundTasks, request_id: str = "default"):
    background_tasks.add_task(write_log, f"请求处理完成: {request_id}")
    return {"logged": True}

@app.get("/items/{item_id}")
async def get_item(item_id: int, logger=Depends(log_request)):
    return {"item_id": item_id, "logged": logger["logged"]}

带状态的后台任务

使用类封装后台任务逻辑,支持状态跟踪和错误处理。

python 复制代码
import logging
from typing import Optional

logger = logging.getLogger("app.tasks")

class EmailTask:
    def __init__(self, email: str, subject: str, content: str):
        self.email = email
        self.subject = subject
        self.content = content
        self.status: str = "pending"

    def __call__(self):
        try:
            self.status = "running"
            import time
            time.sleep(2)
            print(f"发送邮件到 {self.email}: {self.subject}")
            self.status = "completed"
        except Exception as e:
            self.status = "failed"
            logger.error(f"邮件发送失败: {e}")

@app.post("/send-email")
async def send_email_api(
    message: MessageCreate,
    background_tasks: BackgroundTasks
):
    task = EmailTask(message.email, message.subject, message.content)
    background_tasks.add_task(task)

    return {
        "message": "邮件任务已提交",
        "email": message.email
    }

Background Tasks vs Celery

对比项 Background Tasks Celery
依赖 FastAPI 内置 需安装 Celery + Redis/RabbitMQ
持久化 无(进程重启丢失) 有(任务持久化到队列)
重试 不支持自动重试 支持自动重试
分布式 单进程 多 worker 分布式执行
监控 Flower 等监控工具
定时任务 不支持 支持
适用场景 简单、轻量任务 复杂、可靠性要求高的任务

提示:Background Tasks 适合发邮件、写日志等简单场景。如果需要任务持久化、重试、定时或分布式执行,应使用 Celery(见第6步-1-3-部署与实战(file:///d:/kaicofile/trae/study_python/第6步-1-3-FastAPI框架-部署与实战.md))。


6.1.10 Static Files(静态文件托管)

简述 :FastAPI 通过 StaticFiles 中间件托管静态资源文件(CSS、JS、图片等),适用于前后端不分离项目或需要提供文件下载服务的场景。

概念:静态文件

在 Web 应用中,CSS、JavaScript、图片等静态资源需要通过 HTTP 服务提供给客户端。FastAPI 使用 Starlette 的 StaticFiles 组件挂载静态文件目录。

基础用法

使用 app.mount() 挂载静态文件目录,指定 URL 路径前缀和文件系统路径。

bash 复制代码
pip install aiofiles
python 复制代码
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/")
async def root():
    return {
        "css": "/static/css/style.css",
        "js": "/static/js/app.js",
        "image": "/static/images/logo.png"
    }

目录结构

复制代码
myproject/
├── static/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── app.js
│   └── images/
│       └── logo.png
├── main.py

多个静态目录与媒体文件

分别挂载静态资源和用户上传的媒体文件,使用不同的 URL 路径前缀区分。

python 复制代码
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import os

app = FastAPI()

os.makedirs("static", exist_ok=True)
os.makedirs("media", exist_ok=True)

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/media", StaticFiles(directory="media"), name="media")

返回 HTML 页面

使用 HTMLResponse 返回 HTML 页面,结合静态文件实现简单的页面渲染。

python 复制代码
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/", response_class=HTMLResponse)
async def index():
    return """
    <!DOCTYPE html>
    <html>
    <head>
        <link rel="stylesheet" href="/static/css/style.css">
    </head>
    <body>
        <h1>FastAPI 静态文件示例</h1>
        <img src="/static/images/logo.png" alt="Logo">
        <script src="/static/js/app.js"></script>
    </body>
    </html>
    """

提示 :前后端分离项目中,前端通常由 Nginx 或 CDN 托管,不需要 FastAPI 的 StaticFiles。此功能主要用于:1)提供上传文件的访问服务;2)小型项目或管理后台的简单页面渲染。


📖 下一步FastAPI框架-高级部分 数据库集成、认证与安全、WebSocket、SSE、限流、Alembic 迁移等

相关推荐
llilay12 小时前
企业级FastAPI后端模板搭建(三)整合日志Log
数据库·python·fastapi
llilay13 小时前
企业级FastAPI后端模板搭建(一)初始化项目
fastapi
kaico201813 小时前
FastAPI 框架 - 高级部分
oracle·fastapi
kaico201814 小时前
FastAPI 框架 - 部署与实战
fastapi
llilay15 小时前
企业级FastAPI后端模板搭建(二)整合路由Router
开发语言·python·fastapi
我叫张小白。15 小时前
Redis的缓存雪崩、击穿、穿透和解决方案
数据结构·redis·fastapi·缓存穿透·缓存击穿·雪崩·热点key问题
还是鼠鼠1 天前
AI掘金头条新闻系统 (Toutiao News)-获取用户信息
后端·python·mysql·fastapi·web
我叫张小白。2 天前
基于Redis与FastAPI的分布式共享会话体系
数据库·redis·分布式·缓存·中间件·fastapi·依赖注入