【Python】2026版——FastAPI 框架快速搭建后端服务

框架初体验

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,专为在 Python 中构建 RESTful API 而设计。

FastAPI 特点

  • 高性能: 基于Starlette和Pydantic,利用异步(asynchronous)编程,提供出色的性能。
  • 自动文档生成: 自动生成交互式API文档,支持Swagger UI和ReDoc,让API的理解和测试更加直观。
  • 类型注解支持: 利用Python的类型提示,提供更严格的输入验证和更好的代码提示。
  • 异步支持: 支持异步请求处理,使得处理IO密集型任务更加高效。

FastAPI 适用场景

  • 构建API后端: 用于构建RESTful API,支持前后端分离的Web应用。
  • 微服务架构: 可以作为微服务的后端框架,支持快速开发和部署。
  • 数据处理API: 适用于处理数据,接收和返回JSON数据。
  • 实时通信: 支持WebSocket,适用于实时通信场景。

依赖安装

FastAPI 依赖 Python 3.8 及更高版本。

检查当前python版本:

bash 复制代码
# 检查 Python 版本
python --version
# 或者
python3 --version

安装 FastAPI 很简单,这里我们使用 pip 命令来安装。

复制代码
pip install fastapi

也可以使用以下命令直接安装 FastAPI 及所有可选依赖:

arduino 复制代码
pip install "fastapi[all]"

这会安装:

  • fastapi - FastAPI 框架
  • uvicorn[standard] - ASGI 服务器
  • python-multipart - 表单和文件上传支持
  • jinja2 - 模板引擎
  • python-jose - JWT 令牌支持
  • passlib - 密码哈希
  • bcrypt - 密码加密
  • python-dotenv - 环境变量支持

另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:

arduino 复制代码
pip install "uvicorn[standard]"

启动应用

创建一个名为 main.py 的文件,添加以下代码:

python 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

在命令行中运行以下命令以启动应用:

css 复制代码
uvicorn main:app --reload

现在,打开浏览器并访问 **http://127.0.0.1:8000**,你应该能够看到 FastAPI 自动生成的交互式文档,并在根路径 ("/") 返回的 JSON 响应。

!NOTE

代码解析:

  • from fastapi import FastAPI: 这行代码从< code>fastapi 模块中导入了 FastAPI 类。FastAPI 类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。
  • app = FastAPI():这行代码创建了一个 FastAPI 应用实例。
  • @app.get("/"): 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。
  • def read_root():: 这是定义了一个名为 read_root 的函数,它将被调用当用户使用 GET 方法访问根 URL 时。
  • return {"Hello": "World"}: 这行代码是 read_root 函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。

集成环境创建项目

除了通过依赖下载之外,我们还可以用Pycharm集成直接创建项目:

交互式文档

FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。

这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。

通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率

在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。

默认情况下,你可以通过访问 http://127.0.0.1:8000/docs 来打开 Swagger UI 风格的文档,或者通过 http://127.0.0.1:8000/redoc 来打开 ReDoc 风格的文档。

基本路由

根路径路由

创建 FastAPI 实例和根路径路由:

js 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def Hello():
    return {"Hello": "World"}

代码说明:

  • FastAPI():创建 FastAPI 应用实例。
  • @app.get("/"):使用 @app.get 装饰器创建一个处理根路径的路由。
  • def Hello():路由处理函数,返回一个包含 {"Hello": "World"} 的字典。

路径参数

设置路由参数:

js 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/{id}")
def GetId(id: int,query: Union[str, None] = None):
    return {"id": id, "query": query}

代码说明:

  • @app.get("/{id}"):定义了一个路由路径,其中 {id} 是路径参数,对应于函数参数 id
  • def GetId(id: int, query: str = None):路由处理函数接受一个整数类型的路径参数 id 和一个可选的字符串类型查询参数 query
  • 使用函数参数声明查询参数。例如,query: str = None 表示 query 是一个可选的字符串类型查询参数,默认值为 None

启动应用和测试路由:

访问 http://127.0.0.1:8000 查看根路径的响应:

js 复制代码
{
  "Hello": "World"
}

访问 http://127.0.0.1:8000/2?query=ABC 查看带路径参数和查询参数的响应:

js 复制代码
{
  "id": 2,
  "query": "ABC"
}

参数的分类

  1. 路径参数
    • 位置:属于URL路径的一部分/app/{id}
    • 作用:指定唯一的、特定的资源
    • 方法:GET
  2. 查询参数
    • 位置:URL后接?id=1&key=2
    • 作用:对资源集合进行过滤、排序、分页等操作
    • 方法:GET
  3. 请求体参数
    • 位置:HTTP请求的消息体body
    • 作用:创建、更新资源携带大量数据
    • 方法:POSTPUT

类型注解Path

在 FastAPI 中,Path 是一个用于定义和验证路径参数 (Path Parameters)的函数。它属于 fastapi 模块,专门用于从 URL 路径中提取变量,并对其进行类型检查、数据验证和文档生成。

使用 Path 可以提供更强大的功能:

  1. 添加元数据:设置标题、描述、示例值等,用于自动生成 API 文档(Swagger UI / ReDoc)。
  2. 数据验证 :设置最小值 (gt, ge)、最大值 (lt, le)、正则表达式 (regex)、长度限制等。
  3. 明确声明 :当路径参数没有默认值时,必须使用 Path(...) 来显式声明该参数是必需的(尽管在现代 FastAPI 版本中,仅靠类型注解通常也能工作,但使用 Path 是最佳实践,特别是需要验证时)。

基本用法:

  1. 作为类型提示的替代或者补充 :如果你只需要类型检查,不需要额外的验证或者文档描述,可以直接使用类型注解。写作Path(...)

    js 复制代码
    @app.get("/{id}")
    def GetId(id: int = Path(..., title="物品ID", description="物品ID的描述")):
        return {"id": id}
  2. 添加数据验证:可以限制数值的范围或者字符串的格式:

    js 复制代码
    @app.get("/{id}")
    def GetId(id: int = Path(
        ...,
        title="物品ID",
        description="物品ID的描述",
        ge=1,  # 最小值大于等于1
        le=100,  # 最大值小于等于100
        example=5  # 文档中的示例值
    )
    ):
        return {"id": id}
    • gt=0: Great Than ( > 0 )
    • ge=0: Great or Equal ( >= 0 )
    • lt=100: Less Than ( < 100 )
    • le=100: Less or Equal ( <= 100 )
  3. 字符串验证 :对于字符串类型的路径参数,可以使用regex进行格式正则表达式校验

    js 复制代码
    @app.get("/{id}")
    def GetId(id: str = Path(
        ...,
        title="物品ID",
        description="物品ID的描述",
        min_length=3,
        max_length=20,
        regex=r"^[a-zA-Z0-9_-]+$",  # 只允许字母、数字、下划线和连字符
    
    )
    ):
        return {"id": id}
  4. 现代写法 :推荐使用typing.Annotated将类型和校验逻辑分开,这样代码更加清晰,也方便复用验证逻辑:

    js 复制代码
    from typing import Annotated
    
    # 定义验证逻辑
    ItemId = Annotated[int, Path(title="物品ID", gt=0, le=1000)]
    
    @app.get("/{id}")
    def GetId(id: ItemId):
        return {"id": id}

常见参数列表:

参数 说明 示例
default 默认值。对于路径参数,通常设为 ... 表示必需。 Path(...)
title 字段的标题,显示在 API 文档中。 title="用户ID"
description 字段的描述,显示在 API 文档中。 description="用户的唯一标识"
gt 数值必须大于此值。 gt=0
ge 数值必须大于或等于此值。 ge=1
lt 数值必须小于此值。 lt=100
le 数值必须小于或等于此值。 le=100
min_length 字符串的最小长度。 min_length=3
max_length 字符串的最大长度。 max_length=50
regex 用于验证字符串的正则表达式。 regex=r"^text.*"
example 在文档中显示的示例值。 example=123
deprecated 标记该参数是否已弃用。 deprecated=True

类型注解Query

在 FastAPI 中,Query 是用于定义和验证查询参数 (Query Parameters)的函数。查询参数是 URL 中 ? 后面的键值对,例如 /items/?skip=0&limit=10 中的 skiplimit

Path 类似,Query 允许你:

  1. 设置默认值:决定参数是可选的还是必需的。
  2. 数据验证:限制数值范围、字符串长度、正则匹配等。
  3. 生成文档:为 Swagger UI / ReDoc 提供标题、描述、示例等信息。
  4. 类型转换 :自动将字符串类型的查询参数转换为指定的 Python 类型(如 int, float, bool, list 等)。

基本用法:

  1. 可选参数 :如果给参数赋予了默认值(例如None或者具体数字),FastAPI会自动将其识别为可选的查询参数

    js 复制代码
    @app.get("/{id}/")
    def GetId(id: ItemId,
              skip: int = Query(default=0, description="跳过的物品数量"),
              limit: int = Query(default=10, description="返回的最大物品数量")
              ):
        return {"id": id,
                "info": {
                    "skip": skip, "limit": limit
                }
            }

    测试:127.0.0.1:8000/1/?skip=10&limit=1000

    js 复制代码
    {
      "id": 1,
      "info": {
        "skip": 10,
        "limit": 1000
      }
    }
  2. 必需参数 :如果查询参数出必需的,你需要将默认值设置为...

    js 复制代码
    int = Query(..., description="跳过的物品数量")
  3. 高级验证Query支持所有 Pydantic 的验证规则。

    js 复制代码
    from fastapi import FastAPI, Query
    from typing import Annotated
    
    app = FastAPI()
    
    @app.get("/users/")
    async def get_users(
        # 使用 Annotated 写法 (推荐)
        age: Annotated[
            int | None, 
            Query(
                title="用户年龄",
                description="过滤特定年龄的用户",
                gt=0,       # 大于 0
                lt=120,     # 小于 120
                example=25  # 文档示例
            )
        ] = None,
        
        # 传统写法
        region: str = Query(
            default="cn",
            regex=r"^(cn|us|eu)$", # 只允许 cn, us, eu
            description="地区代码"
        )
    ):
        return {"age": age, "region": region}
列表类型查询参数

列表类型的查询参数:可以自动处理重复的查询参数,将其转换成列表:

js 复制代码
@app.get("/{id}/")
def GetId(id: ItemId,
          item_ids: Annotated[List[int], Query(title="物品ID列表")],
):
    return {"id": id,
            "list": item_ids
            }
  • 注意:URL 中需要多次使用同一个键(?item_ids=1&item_ids=2&item_ids=4),或者使用逗号分隔(取决于客户端和配置,默认是多次键)。

    js 复制代码
    {
      "id": 1,
      "list": [2, 3, 4]
    }
现代写法

现代写法 :同样将类型信息与验证逻辑分离,使用typing.Annotated

js 复制代码
# 定义验证逻辑
ItemId = Annotated[int, Path(title="物品ID", gt=0, le=1000)]

PaginationSkip = Annotated[int, Query(ge=0, description="跳过记录数", example=0)]
PaginationLimit = Annotated[int, Query(ge=1, le=100, description="每页记录数", example=20)]
SearchQuery = Annotated[str | None, Query(min_length=3, description="搜索关键词")]

@app.get("/{id}/")
def GetId(id: ItemId,
          skip: PaginationSkip = 0,  # 可以直接用类型,也可以覆盖默认值
          limit: PaginationLimit = 20,
          q: SearchQuery = None
          ):
    return {"id": id,
            "skip": skip,
            "limit": limit,
            "query": q
            }

类型注解Field

在 FastAPI 中,Field 是用于定义 Pydantic 模型(数据模型)内部字段 的验证规则和元数据的函数。

它与 PathQuery 的核心区别在于作用域

  • Path / Query / Body / Header / Cookie:用于定义 API 接口函数参数 的来源和验证。
  • Field:用于定义 Pydantic 模型类内部属性 的验证规则和文档信息。

当你定义一个继承自 BaseModel 的类(用于请求体 Request Body 或响应体 Response Body)时,你需要使用 Field 来告诉 Pydantic/FastAPI 如何验证该字段的数据,以及如何在 API 文档中展示它。

基本用法

Field 通常作为类型注解的默认值使用。

接下来创建了一个 /items/ 路由,使用 @app.post 装饰器表示这是一个处理 POST 请求的路由:

js 复制代码
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: str = Field(..., description="物品的名称", min_length=3, max_length=50)
    price: float = Field(..., gt=0, description="物品价格,必须大于0")
    tags: list[str] = Field(default_factory=list, description="物品标签列表")

@app.post("/items/")
async def create_item(item: Item):
    return item

使用 Pydantic 模型 Item 定义了一个请求体,包含多个字段,其中一些有默认值,更多 Pydantic 介绍参考:FastAPI Pydantic 模型

数据验证

Field 支持所有 Pydantic 的验证器,这与 Path/Query 非常相似:

  • 数值 : gt, ge, lt, le, multiple_of
  • 字符串 : min_length, max_length, regex (或 pattern)
  • 列表/集合 : min_items, max_items (在 Pydantic V2 中已改为 min_length, max_length)
  • 其他 : title, description, example, examples, deprecated
js 复制代码
from pydantic import BaseModel, Field, EmailStr

class UserCreate(BaseModel):
    email: EmailStr = Field(..., description="用户邮箱")
    age: int = Field(
        ..., 
        gt=18, 
        lt=99, 
        description="用户年龄必须在 18 到 99 之间"
    )
    password: str = Field(
        ..., 
        min_length=8, 
        regex=r"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$", # 简单示例:必须包含字母和数字
        description="密码至少8位,包含字母和数字"
    )

!TIP

BaseModel

BaseModelPydantic 库的核心类,用于数据验证和序列化。它让你可以定义数据模型,并自动验证数据的类型和规则。

js 复制代码
from pydantic import BaseModel

class User(BaseModel):
 name: str
 age: int
 email: str

# 自动验证和转换
user = User(name="张三", age=25, email="zhangsan@example.com")
print(user.name)  # 输出: 张三
print(user.age)   # 输出: 25

EmailStr

EmailStr 是 Pydantic 提供的特殊类型 ,用于验证电子邮件格式。它会自动检查字符串是否符合电子邮件格式。

js 复制代码
from pydantic import BaseModel, EmailStr

class User(BaseModel):
 email: EmailStr  # 必须是有效的邮箱格式

# ✅ 正确示例
user1 = User(email="test@example.com")     # 成功
user2 = User(email="user@gmail.com")       # 成功

# ❌ 错误示例
user3 = User(email="not-an-email")         # 报错: 无效的邮箱格式
user4 = User(email="test@")                 # 报错: 缺少域名
user5 = User(email="@example.com")          # 报错: 缺少用户名
字段别名

这是 Field 的一个非常强大的功能。它允许 Python 变量名与 JSON 数据中的键名(如 userNameuser-name)不一致。

js 复制代码
from pydantic import BaseModel, Field

class Product(BaseModel):
    # Python 代码中使用 product_id
    # JSON 请求体中必须使用 "product-id"
    product_id: int = Field(..., alias="product-id")
    
    # Python 代码中使用 item_name
    # JSON 请求体中可以使用 "itemName" (驼峰命名)
    item_name: str = Field(..., alias="itemName")

# 注意:默认情况下,FastAPI 会使用 alias 来解析传入的数据。
# 如果需要同时支持 alias 和 变量名,可以设置 populate_by_name=True (Pydantic V2)
常量与固定值

如果你希望某个字段在模型中始终是一个固定值,可以使用default固定值并配合frozen=True(可选)

js 复制代码
class Cat(BaseModel):
    pet_type: str = Field("cat", title="宠物类型", frozen=True) # 始终是 "cat"
    name: str

class Dog(BaseModel):
    pet_type: str = Field("dog", title="宠物类型", frozen=True) # 始终是 "dog"
    name: str
现代写法

官方推荐使用 typing.Annotated 将类型定义与验证逻辑分离。这使得代码更清晰,且方便复用验证规则。

传统写法:

js 复制代码
class Item(BaseModel):
 price: float = Field(..., gt=0, description="价格")

现代写法:

js 复制代码
from typing import Annotated
from pydantic import BaseModel, Field

# 定义可复用的类型约束
PositiveFloat = Annotated[float, Field(gt=0, description="必须为正数")]
ShortString = Annotated[str, Field(min_length=1, max_length=50)]

class Item(BaseModel):
    price: PositiveFloat
    name: ShortString
    quantity: Annotated[int, Field(ge=0, default=1)] = 1  # 可以结合默认值

Field vs Path/Query/Body 对比表

特性 Field Path / Query / Body
位置 Pydantic 模型类内部 (class Item(BaseModel)) 路径操作函数参数 (def func(param: type = ...))
用途 定义数据结构内部的验证规则和元数据 定义 HTTP 请求参数的来源(路径、查询、Body等)及验证
典型场景 请求体 (Request Body) 或 响应体 (Response Body) 的字段定义 接收 URL 参数、查询字符串、Header 等
别名 (Alias) 常用于处理 JSON 键名与 Python 变量名不一致 也可用,但主要用于处理特殊字符参数名
依赖关系 Body (隐式或显式) 使用 直接使用

响应数据

返回类型

默认情况下,FastAPI 会自动将路径操作函数返回的 Python 对象(字典、列表、Pydantic 模型等),经由 jsonable_encoder 转换为 JSON 兼容格式,并包装为 JSONResponse 返回。这省去了手动序列化的步骤,让开发者能更专注于业务逻辑。 如果需要返回非 JSON 数据(如 HTML、文件流),FastAPI 提供了丰富的响应类型来返回不同数据

响应类型设置方式

在 FastAPI 中,设置响应类型(Response Type)主要有两种方式:函数返回类型注解路径操作装饰器参数

  1. 注解:直接在 defasync def 后面使用 -> 指定返回类型

    js 复制代码
    class Item(BaseModel):
        name: str
        price: float
        is_offer: bool | None = None
    
    @app.get("/items/{item_id}")
    async def read_item(item_id: int) -> Item:
        # FastAPI 会自动将返回的字典转换为 Item 模型,并验证结构
        return {"name": "Foo", "price": 50.2, "is_offer": True}
  2. response_model 参数 :在路径操作装饰器(如 @app.get)中显式传递 response_model 参数

    js 复制代码
    class Item(BaseModel):
        name: str
        price: float
    
    @app.get("/items/", response_model=List[Item])
    async def read_items():
        # 返回一个列表,FastAPI 会验证列表中的每个元素
        return [
            {"name": "Foo", "price": 50.2},
            {"name": "Bar", "price": 60.1}
        ]

返回JSON 格式

路由处理函数返回一个字典,该字典将被 FastAPI 自动转换为 JSON 格式,并作为响应发送给客户端:

js 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

返回Pydantic 模型

路由处理函数返回一个 Pydantic 模型实例,FastAPI 将自动将其转换为 JSON 格式,并作为响应发送给客户端:

js 复制代码
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items/")
def create_item(item: Item):
    return item

自定义响应数据格式

response_model 是路径操作装饰器(如 @app.get@app.post)的关键参数,它通过一个 Pydantic 模型来严格定义和约束 API 端点的输出格式。这一机制在提供自动数据验证和序列化的同时,更是保障数据安全性的第一道防线。

请求头和Cookie

使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据。

js 复制代码
from fastapi import Header, Cookie
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
    return {"User-Agent": user_agent, "Session-Token": session_token}

异常处理

使用 HTTPException 抛出异常,返回自定义的状态码和详细信息。

以下实例在 item_id42 会返回 404 状态码:

js 复制代码
from fastapi import HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id == 42:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id}

重定向和状态码

RedirectResponse

使用 RedirectResponse 实现重定向,将客户端重定向到 /items/ 路由。

js 复制代码
from fastapi import Header, Cookie
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/redirect")
def redirect():
    return RedirectResponse(url="/items/")

@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
    return {"User-Agent": user_agent, "Session-Token": session_token}

以上代码在浏览器访问 http://127.0.0.1:8000/redirect/ 会自动跳转到 http://127.0.0.1:8000/items/ 页面

处理多个状态码

如果一个接口可能返回多种状态码(如 200 成功,404 未找到),且它们的响应结构不同,可以使用 responses 参数。

js 复制代码
class Item(BaseModel):
    name: str

class ErrorResponse(BaseModel):
    detail: str

@app.get(
    "/items/{item_id}",
    response_model=Item,  # 默认成功响应 (200)
    responses={
        404: {
            "description": "物品未找到",
            "model": ErrorResponse, # 404 的响应模型
            "content": {
                "application/json": {
                    "example": {"detail": "物品 ID 999 不存在"}
                }
            }
        },
        403: {
            "description": "无权访问",
            "content": {
                "application/json": {
                    "example": {"detail": "权限不足"}
                }
            }
            # 如果没有 model,则只生成文档,不进行数据验证/过滤
        }
    }
)
async def read_item(item_id: int):
    if item_id == 999:
        # 直接抛出 HTTPException,FastAPI 会使用上面定义的 404 模型生成文档
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="物品 ID 999 不存在")
    
    return {"name": "My Item"}

自定义响应头

使用 JSONResponse 自定义响应头:

js 复制代码
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    content = {"item_id": item_id}
    headers = {"X-Custom-Header": "custom-header-value"}
    return JSONResponse(content=content, headers=headers)

以上代码在浏览器访问 http://127.0.0.1:8000/items/42/ 页面显示如下,可以看到我们自定义的响应头

路径操作依赖项

FastAPI 提供了路径操作依赖项的机制,允许你在路由处理函数执行之前或之后运行一些额外的逻辑,依赖项就是一个函数,且可以使用与路径操作函数相同的参数。路径操作依赖项提供了一种灵活的方式来组织代码、验证输入、进行身份验证等。

依赖项

依赖项:是在路由操作函数执行前或后运行的可复用的函数或对象。

它们被用于执行一些通用的逻辑,如验证、身份验证、数据库连接等。在 FastAPI 中,依赖项通常用于两个方面:

  • 预处理(Before)依赖项: 在路由操作函数执行前运行,用于预处理输入数据,验证请求等。
  • 后处理(After)依赖项: 在路由操作函数执行后运行,用于执行一些后处理逻辑,如日志记录、清理等。

依赖注入

在 FastAPI 中,通过在路由操作函数参数中声明依赖项来实现依赖注入。

使用方法:

  1. 定义依赖项

    js 复制代码
    # 依赖项函数
    def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
        return {"q": q, "skip": skip, "limit": limit}
  2. 使用依赖项

    js 复制代码
    from fastapi import Depends
    
    # 路由操作函数
    @app.get("/items/")
    async def read_items(commons: dict = Depends(common_parameters)):
        return commons

在这个例子中,read_items 路由操作函数中的参数 commons 使用了 Depends(common_parameters),表示 common_parameters 是一个依赖项。FastAPI 将在执行路由操作函数之前运行 common_parameters 函数,并将其返回的结果传递给 read_items 函数。

请求路径:

js 复制代码
# 1. 最基本的请求
curl "http://localhost:8000/items/"
# 返回: {"q": null, "skip": 0, "limit": 100}

# 2. 搜索关键词
curl "http://localhost:8000/items/?q=手机"
# 返回: {"q": "手机", "skip": 0, "limit": 100}

# 3. 分页查询
curl "http://localhost:8000/items/?skip=20&limit=10"
# 返回: {"q": null, "skip": 20, "limit": 10}

# 4. 搜索加分页
curl "http://localhost:8000/items/?q=电脑&skip=30&limit=15"
# 返回: {"q": "电脑", "skip": 30, "limit": 15}

预处理

以上👆实例中,common_parameters 是一个依赖项函数,它接受查询参数 q、skip 和 limit,并返回一个包含这些参数的字典。

在路由操作函数 read_items 中,通过传入 Depends(common_parameters),我们使用了这个依赖项函数,实现了在路由执行前预处理输入数据的功能。

后处理

以下例子中,after_request 是一个后处理函数,用于在路由执行后执行一些逻辑。

在路由操作函数 read_items_after 中,通过传入 Depends(after_request),我们使用了这个后处理依赖项,实现了在路由执行后进行额外操作的功能。

js 复制代码
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# 后处理函数
async def after_request():
    # 这里可以执行一些后处理逻辑,比如记录日志
    pass

# 后处理依赖项
@app.get("/items/", response_model=dict)
async def read_items_after(request: dict = Depends(after_request)):
    return {"message": "Items returned successfully"}

多依赖组合

以下例子中,common_parametersverify_token 是两个不同的依赖项函数,verify_token 依赖于 common_parameters,这种组合依赖项的方式允许我们在路由执行前先验证一些参数,然后在进行身份验证。

js 复制代码
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# 依赖项函数1
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

# 依赖项函数2
def verify_token(token: str = Depends(common_parameters)):
    if token is None:
        raise HTTPException(status_code=400, detail="Token required")
    return token

# 路由操作函数
@app.get("/items/")
async def read_items(token: dict = Depends(verify_token)):
    return token

表单数据

在 FastAPI 中,接收表单数据是一种常见的操作,通常用于处理用户通过 HTML 表单提交的数据。

FastAPI 提供了 Form 类型,可以用于声明和验证表单数据。

声明表单数据

接下来我们设计一个接收一个登陆的表单数据,要使用表单,需预先安装 python-multipart

js 复制代码
pip install python-multipart

添加代码:

js 复制代码
from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
    return {"username": username}

接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验。

使用 Pydantic 模型来声明表单数据模型。

在模型中,使用 Field 类型声明每个表单字段,并添加必要的验证规则。

js 复制代码
from fastapi import Form, Depends
from pydantic import BaseModel, Field

# 定义模型
class Item(BaseModel):
    name: str = Field(..., title="Item Name", max_length=100)
    description: str = Field(None, title="Item Description", max_length=255)
    price: float = Field(..., title="Item Price", gt=0)

# 创建依赖项函数
async def parse_item_form(
    name: str = Form(..., max_length=100),
    description: Optional[str] = Form(None, max_length=255),
    price: float = Form(..., gt=0)
) -> Item:
    # 将收集到的表单数据传递给 Pydantic 模型进行验证和实例化
    return Item(name=name, description=description, price=price)

# 在路径中使用
app = FastAPI()

@app.post("/items/")
async def create_item(item: Item = Depends(parse_item_form)):
    # 此时 item 已经是验证过的 Item 模型实例了
    return {"message": "Success", "data": item}

Model中的 max_length=100(Pydantic验证):

js 复制代码
class Item(BaseModel):
 name: str = Field(..., title="Item Name", max_length=100)
 # 这是 Pydantic 的 Field,用于:
 # - 数据验证(当创建 Item 实例时)
 # - 生成 JSON Schema
 # - API 文档

Form依赖中的 max_length=100(FastAPI表单验证):

js 复制代码
async def parse_item_form(
 name: str = Form(..., max_length=100),  # 这是 FastAPI 的 Form
 # 这是 FastAPI 的表单验证:
 # - 在请求到达时立即验证表单数据
 # - 如果超过100字符,直接返回422错误
 # - 不会继续执行函数
) -> Item:
 return Item(name=name, ...)  # 此时 name 已经确保不超过100字符

接收表单数据

在路由操作函数中,可以使用 Form 类型来接收表单数据。

Form 类型的参数可以与 Pydantic 模型的字段一一对应,以实现表单数据的验证和转换。

js 复制代码
from fastapi import FastAPI, Form

app = FastAPI()

# 路由操作函数
@app.post("/items/")
async def create_item(
    name: str = Form(...),
    description: str = Form(None),
    price: float = Form(..., gt=0),
):
    return {"name": name, "description": description, "price": price}

以上例子中,create_item 路由操作函数接收了三个表单字段:name、description 和 price,这些字段与 Item 模型的相应字段一致,FastAPI 将自动根据验证规则验证表单数据。

接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验。

文件上传

如果表单包含文件上传,可以使用 UploadFile 类型处理。

以下是一个处理文件上传的实例:

js 复制代码
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

# 路由操作函数
@app.post("/files/")
async def create_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

在这个例子中,create_file 路由操作函数接收了一个 UploadFile 类型的文件参数。

FastAPI 将负责处理文件上传,并将文件的相关信息包装在 UploadFile 对象中,可以轻松地获取文件名、内容类型等信息。

核心概念

FastAPI 是一个现代、快速(高性能)的 Python Web 框架,用于构建 API。它基于标准 Python 类型提示,使用 Starlette 和 Pydantic 构建。

异步编程

同步与异步核心概念:

js 复制代码
import asyncio
import time
import httpx

# 同步方式 - 阻塞执行
def sync_fetch_data():
 start_time = time.time()
 # 模拟三个网络请求
 time.sleep(1)  # 第一个请求
 time.sleep(1)  # 第二个请求
 time.sleep(1)  # 第三个请求
 print(f"同步执行耗时: {time.time() - start_time:.2f}秒")  # 约3秒

# 异步方式 - 并发执行
async def async_fetch_data():
 start_time = time.time()
 # 三个请求并发执行
 await asyncio.gather(
     asyncio.sleep(1),  # 第一个请求
     asyncio.sleep(1),  # 第二个请求
     asyncio.sleep(1),  # 第三个请求
 )
 print(f"异步执行耗时: {time.time() - start_time:.2f}秒")  # 约1秒

# 运行示例
sync_fetch_data()  # 输出: 同步执行耗时: 3.00秒
asyncio.run(async_fetch_data())  # 输出: 异步执行耗时: 1.00秒

ASGI是 Python 异步 Web 服务器和应用程序之间的标准接口。

js 复制代码
# WSGI 应用(同步)- 传统 Flask 风格
def wsgi_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [b'Hello World']

# ASGI 应用(异步)- FastAPI 风格
async def asgi_app(scope, receive, send):
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [(b'content-type', b'text/plain')],
    })
    await send({
        'type': 'http.response.body',
        'body': 'Hello World',
    })

FastAPI 中的异步路径操作:

js 复制代码
from fastapi import FastAPI
import asyncio

app = FastAPI()

# 同步路径操作
@app.get("/sync")
def sync_endpoint():
    # 同步操作会阻塞整个应用
    time.sleep(2)
    return {"message": "同步响应"}

# 异步路径操作(推荐)
@app.get("/async")
async def async_endpoint():
    # 异步操作不会阻塞其他请求
    await asyncio.sleep(2)
    return {"message": "异步响应"}

!TIP

何时使用异步?

适合异步的场景:

  • 数据库操作
  • 网络请求(API 调用)
  • 文件 I/O 操作
  • 长时间等待的操作

不适合异步的场景:

  • CPU 密集型计算
  • 简单的数据处理
  • 没有 I/O 等待的操作

REST API

REST是一种 Web API 设计风格,强调:

  • 资源导向:URL 表示资源
  • 状态无关:每个请求都是独立的
  • 统一接口:使用标准 HTTP 方法
  • 分层系统:支持缓存、负载均衡等

Http方法

js 复制代码
from fastapi import FastAPI, status, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

# GET - 安全且幂等
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    """获取资源,不修改服务器状态"""
    user = find_user(user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found"
        )
    return user

# POST - 不安全且非幂等
@app.post("/users", status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
    """创建新资源"""
    if user_exists(user.email):
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User already exists"
        )
    new_user = create_new_user(user)
    return new_user

# PUT - 不安全但幂等
@app.put("/users/{user_id}")
async def replace_user(user_id: int, user: UserUpdate):
    """完整替换资源"""
    if not user_exists(user_id):
        # PUT 可以创建资源
        return create_user_with_id(user_id, user)
    return replace_existing_user(user_id, user)

# PATCH - 不安全且通常非幂等
@app.patch("/users/{user_id}")
async def update_user(user_id: int, user: UserPatch):
    """部分更新资源"""
    existing_user = find_user(user_id)
    if not existing_user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found"
        )
    return patch_user_fields(existing_user, user)

# DELETE - 不安全但幂等
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: int):
    """删除资源"""
    if not user_exists(user_id):
        # 幂等性:删除不存在的资源仍返回成功
        return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)
    delete_existing_user(user_id)
    return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)

Http状态码

常用状态码速查:

js 复制代码
# 成功状态码
200 OK                    # 请求成功
201 Created              # 资源创建成功
204 No Content           # 成功但无内容返回
206 Partial Content      # 部分内容(分页、断点续传)

# 重定向
301 Moved Permanently    # 永久重定向
302 Found               # 临时重定向
304 Not Modified        # 资源未修改(缓存)

# 客户端错误
400 Bad Request         # 请求格式错误
401 Unauthorized        # 未认证
403 Forbidden          # 已认证但无权限
404 Not Found          # 资源不存在
405 Method Not Allowed  # HTTP 方法不允许
409 Conflict           # 资源冲突
422 Unprocessable Entity # 数据验证失败
429 Too Many Requests   # 请求过于频繁

# 服务器错误
500 Internal Server Error # 服务器内部错误
502 Bad Gateway          # 网关错误
503 Service Unavailable  # 服务不可用

JSON数据最佳实践

js 复制代码
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from pydantic import BaseModel, Field
import json

# JSON 数据格式规范
class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    full_name: Optional[str] = None
    is_active: bool = True
    created_at: datetime
    updated_at: Optional[datetime] = None
    
    # JSON 序列化配置
    class Config:
        # 允许使用字段别名
        allow_population_by_field_name = True
        # JSON 编码器
        json_encoders = {
            datetime: lambda v: v.isoformat(),
            Decimal: lambda v: float(v)
        }

# 响应格式标准化
class APIResponse(BaseModel):
    """标准 API 响应格式"""
    success: bool = True
    message: str = "操作成功"
    data: Optional[dict] = None
    errors: Optional[List[str]] = None
    timestamp: datetime = Field(default_factory=datetime.now)

# 分页响应格式
class PaginatedResponse(BaseModel):
    items: List[dict]
    total: int
    page: int
    size: int
    pages: int

@app.get("/users", response_model=PaginatedResponse)
async def get_users(page: int = 1, size: int = 10):
    """返回分页的用户列表"""
    users = get_users_paginated(page, size)
    total = count_users()
    
    return PaginatedResponse(
        items=users,
        total=total,
        page=page,
        size=size,
        pages=(total + size - 1) // size
    )

自动文档生成

js 复制代码
from fastapi import FastAPI, Query, Path, Body
from fastapi.openapi.utils import get_openapi

app = FastAPI(
    title="我的 API",
    description="这是一个示例 API,展示 FastAPI 的功能",
    version="1.0.0",
    terms_of_service="http://example.com/terms/",
    contact={
        "name": "开发者",
        "url": "http://example.com/contact/",
        "email": "developer@example.com",
    },
    license_info={
        "name": "MIT",
        "url": "https://opensource.org/licenses/MIT",
    },
)

@app.get(
    "/users/{user_id}",
    summary="获取用户信息",
    description="根据用户 ID 获取用户的详细信息",
    response_description="用户信息对象",
    tags=["用户管理"]
)
async def get_user(
    user_id: int = Path(..., title="用户ID", description="要获取的用户ID", ge=1),
    include_posts: bool = Query(False, title="包含文章", description="是否包含用户的文章列表")
):
    """
    获取用户信息:
    
    - **user_id**: 用户的唯一标识符
    - **include_posts**: 是否在响应中包含用户的文章列表
    
    返回用户的基本信息,如果 include_posts 为 True,还会包含文章列表。
    """
    user = find_user(user_id)
    if include_posts:
        user.posts = get_user_posts(user_id)
    return user

# 自定义 OpenAPI schema
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    
    openapi_schema = get_openapi(
        title="自定义 API 文档",
        version="2.5.0",
        description="这是自定义的 OpenAPI schema",
        routes=app.routes,
    )
    
    # 添加自定义信息
    openapi_schema["info"]["x-logo"] = {
        "url": "https://example.com/logo.png"
    }
    
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

项目结构

一个标准的、能长大的 FastAPI 项目结构是这样的(这是 2026 年业界最推荐的模块化结构):

标准项目结构:

  • crud/:数据库增删该查逻辑(封装数据库操作)
  • models/:数据库模型
  • routers/:路由层
  • schemas/:数据验证模型
  • utils/:工具函数
  • config/:配置相关
  • main.py:主入口文件

模块化路由

创建模块化目录结构:

python 复制代码
app/
---routers/
	------路由1/
	------路由2/

编写独立路由模块:

python 复制代码
rom fastapi import APIRouter

# 创建 APIRouter 实例
router = APIRouter(prefix="/api/news",tags=["news"])
@router.get("/categories")
async def get_categories():
    return {"msg": "获取分类成功"
  • prefix:路由前缀
  • tags:交互式文档分组

在主入口文件中注册路由:

python 复制代码
from fastapi import FastAPI
from routers import news

app = FastAPI()

# 注册路由
app.include_router(news.router)

中间件

中间件(Middleware)是一个在每次请求进入 FastAPI 应用时都会被执行的函数。 它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次。

常用的中间件处理情况:

  • 跨域处理
  • 身份认证
  • 响应头处理
  • 性能监控
  • 日志处理

基本使用:

函数的顶部使用装饰器 @app.middleware("http")

语法格式:

js 复制代码
@app.middleware("http")
async def 中间件函数(request,call_next):
	respone = await call_next(request)
	return respone
  • request:请求对象,包含请求的所有信息(headers, body, method, url 等)。
  • call_next:一个可调用对象(函数)。你必须调用它并传入 request,它会将请求传递给后续的中间件或实际的路径操作函数,并返回一个 Response 对象。如果不调用它,请求链条就会中断,用户永远收不到响应。
  • 异步 : 中间件函数必须是 async def
  • 返回值 : 必须返回一个 Response 对象(通常是 call_next 返回的那个,或者是基于它修改后的对象)。

依赖注入和中间件的区别

  • 中间件 :像是一个全局的过滤器 ,包裹着整个应用。它处理的是原始的 HTTP 请求和响应(Request/Response 对象),通常用于与具体业务逻辑无关的基础设施任务(如 CORS、Gzip、全局日志、IP 黑名单)。
  • 依赖注入 :像是路径操作函数的参数准备器 。它在路由被匹配后执行,处理的是Python 对象(如数据库会话、当前用户对象、分页参数),主要用于业务逻辑的解耦和复用。
特性 中间件 (Middleware) 依赖注入 (Depends)
作用范围 全局。对应用接收到的每一个请求生效(除非你在代码里手动写 if 排除某些路径)。 局部。仅对声明了该依赖的特定路径操作函数(或包含该依赖的其他依赖)生效。
操作对象 Starlette/FastAPI 的 RequestResponse 对象。操作的是底层的 HTTP 协议数据(Header, Body, Status Code)。 普通的 Python 对象。你可以返回任何类型(dict, class instance, DB session, str 等)。
执行时机 在路由匹配之前就开始执行(请求阶段),在响应返回给客户端之前最后执行(响应阶段)。 在路由匹配之后,实际的路径操作函数执行之前。
主要用途 跨切面基础设施: • CORS / Gzip • 全局请求/响应日志 • 简单的 IP 拦截 • 修改全局 Header 业务逻辑复用: • 数据库连接管理 • 用户身份验证与授权(获取 current_user) • 参数验证与转换 •

跨域处理

跨域资源共享(CORS)是一种浏览器安全机制,用于允许运行在一个源(Origin)的 Web 应用,通过浏览器向另一 个源的服务器发起跨域 HTTP 请求,并在服务器授权的前提下获取资源。

解决办法:CORS中间件,让后端主动告诉浏览器:这个前端"允许访问"

python 复制代码
from fastapi.middleware.cors import CORSMiddleware

# 允许的来源(可以是域名列表)
origins = [
    "http://localhost",
    "http://localhost:3000",
    "https://your-frontend-domain.com"
]

# 添加 CORS 中间件
app.add_middleware(
    CORSMiddleware,	
    allow_origins=["*"],	# 允许访问的源
    allow_credentials=True,  # 允许携带 Cookie
    allow_methods=["*"],	# 允许所有请求方法
    allow_headers=["*"],	# 允许所有请求
)

ORM

ORM(Object-RelationalMapping,对象关系映射)是一种编程技术,用于在面向对象编程语言和关系型数据库之间 建立映射。它允许开发者通过操作对象的方式与数据库进行交互,而无需直接编写复杂的SQL语句。

优势:

  • 减少重复的 SQL 代码
  • 代码更简洁易读
  • 自动处理数据库连接和事务
  • 自动防止 SQL 注入攻击

ORM配置

js 复制代码
# 安装 SQLAlchemy + asyncio 驱动
pip install sqlalchemy[asyncio] aiomysql

(核心库):这是 Python 最著名的 SQL 工具包和 ORM(对象关系映射)框架

  • asyncio:开启异步支持

  • aiomysql:是一个第三方的、纯 Python 编写的 MySQL 驱动程序

创建数据库引擎

js 复制代码
# 1. 导入 SQLAlchemy 的异步组件
from sqlalchemy.ext.asyncio import create_async_engine

# 2. 配置数据库连接模型 格式:mysql+aiomysql://用户:密码@host:port/库名
DB_URL = "mysql+aiomysql://root:123456@localhost:3306/testdb"

# 3. 创建引擎
DB_ENGINE = create_async_engine(
	DB_URL,
	echo=True, # 是否输出SQL日志
	pool_size = 10, # 设置连接池中保持的持久连接数
	max_overflow = 20 # 设置连接池允许创建的额外连接数
)

定义数据模型

定义基类

基类,继承 DeclarativeBase(包含通用属性和字段的映射)

js 复制代码
from sqlalchemy.orm import DeclarativeBase

# 定义模型类:基类
class Base(DeclarativeBase):
	pass
创建映射

使用 SQLAlchemy 2.0 的 Mappedmapped_column 语法,这是类型安全的新标准。

语法格式:

js 复制代码
列名: Mapped[T] = mapped_column(列属性)

1. Mapped[T]

  • 作用 :这是一个类型提示泛型 。它告诉 Python 类型检查器和 ORM:"这个属性的数据类型是 T"。
  • 位置:放在变量名的左侧,作为类型注解。
  • 示例id: Mapped[int] 表示 id 属性在 Python 代码中是一个整数。

2. mapped_column()

  • 作用 :这是一个函数 ,用于定义数据库列的具体行为(如主键、外键、是否可空、默认值等)。它替代了旧的 Column()
  • 位置:放在变量名的右侧,作为赋值表达式。
  • 示例mapped_column(Integer, primary_key=True) 告诉 SQLAlchemy 在数据库中创建一个整数类型的主键列。
  • 参数mapped_column 的参数非常灵活,主要分为三类:
    1. 类型相关:通常是 SQLAlchemy 的类型类(如 Integer, String)。
    2. 约束相关:这些参数直接映射到 DDL
      • primary_key=True: 主键。
      • unique=True: 唯一约束。
      • nullable=False: 非空约束 (NOT NULL)。
      • index=True: 创建索引。
      • ForeignKey(...): 外键约束。
    3. 默认值相关:
      • default=value: Python 端默认值 。当你执行 user = User() 且没传该字段时生效。如果直接执行 SQL 插入,此值不生效。

路由中使用ORM

创建工厂函数:

js 复制代码
 创建异步 Session 工厂
# class_=AsyncSession: 指定使用异步会话

AsyncSessionLocal = async_sessionmaker(
    bind=DB_ENGINE,  # 绑定数据库引擎
    class_=AsyncSession,  # 指定会话类
    expire_on_commit=False	# 会话对象不过期,不重新查询数据库
)

添加依赖项,用于获取依赖会话

js 复制代码
依赖注入函数 (FastAPI 的核心)
# 这个函数会被 FastAPI 的 Depends() 调用
# 使用 yield 确保会话在使用完毕后自动关闭,即使发生异常

async def Get_Database() -> AsyncSession:
    async with AsyncSessionLocal() as session:
		try:
        	yield session # 返回数据库会话给路由处理函数
            await session.commit() # 无异常提交事务
       	except Exception:
			await session.rollback() # 有异常则回滚
            raise
        finally:
        	await session.close() # 关闭会话

路由中使用:

js 复制代码
@app.get("/book/books")
async def get_book_list(
    db: AsyncSession = Depends(Get_Database)
):
# 数据库操作
pass

项目中配置

通常我们会在项目的config目录下新建一个db.py作为数据库orm配置文件,可以直接拷贝以下👇代码:

python 复制代码
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession, create_async_engine

# 数据库URL
ASYNC_DATABASE_URL = "mysql+aiomysql://数据库账号:数据库密码@localhost:3306/数据库名?charset=utf8mb4"

# 创建异步引擎
DB_Engine = create_async_engine(
    ASYNC_DATABASE_URL,
    echo=True,  # 可选:输出sql日志
    pool_size=10,  # 设置连接池中保持的持久连接数
    max_overflow=20  # 设置连接池允许创建的额外连接数
)

# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(
    bind=DB_Engine,		# 绑定数据库引擎
    class_= AsyncSession,	# 指定会话类
    expire_on_commit=False	# 会话对象不过期,不重新查询数据库
)

# 依赖项,用于获取数据库会话
async def Get_DB():
    async with AsyncSessionLocal() as session:
        try:
            yield session   # 返回数据库会话给路由处理函数
            await session.commit()  # 无异常提交事务
        except Exception:
            await session.rollback()    # 有异常则回滚
            raise
        finally:
            await session.close()   # 关闭会话

查询操作

核心语句:await db.execute( select(模型类) ),返回一个 ORM 对象

查全部

  • scalars().all()

    js 复制代码
    @app.get("/book/get_books")
     async def get_book_list(db: AsyncSession=Depends(get_database)):
     result = await db.execute(select(Book))
     book = result.scalars().all()
     return book

查单个

  • scalars().first()

    js 复制代码
     @app.get("/book/get_book")
     async def get_book(db: AsyncSession=Depends(get_database)):
     book=result.scalars().first() 
     return book
  • get(模型类, 主键值)

    js 复制代码
     @app.get("/book/get_book")
     async def get_book(db: AsyncSession=Depends(get_database)):
     book = await db.get(Book, 1)
     return book

条件查询

语法格式:select(Book).where(条件, 条件2, ...)

条件:

  • 比较判断:==; >; <; >=; <=

    js 复制代码
    app.get("/book/{book_id}")
    async def get_book_list(book_id: int, db: AsyncSession = Depends(get_database)):
     result = await db.execute(select(Book).where(Book.id == book_id))
     book = result.scalar_one_or_none()
     return book

    !TIP

    scalars():提取标量值

    • scalars().all():获取所有结果

    • scalars().first(): 提取第一个数据

    • scalars().one():必须恰好有一个结果

    • scalar_one_or_none(): 提取一个或 null

  • 模糊查询:like()

    js 复制代码
    app.get("/book/get_books")
    async def get_book_list(db: AsyncSession = Depends(get_database)):
     book = result.scalars().all()
     return book
    • %:零个、一个或多个字符:
    • _:一个单个字符
  • 与非查询:&; | ; ~

    js 复制代码
    @app.get("/book/get_books")
    async def get_book_list(db: AsyncSession = Depends(get_database)):
     result = await db.execute(select(Book).where((Book.author == "曹雪芹") & (Book.price == 200)))
     book = result.scalars().all()
     return book
  • 包含查询:in_()

    js 复制代码
    @app.get("/book/get_books")
    async def get_book_list(db: AsyncSession = Depends(get_database)):
     id_list = [1, 2, 3, 4, 5, 6]
     book = result.scalars().all()
     result = await db.execute(select(Book).where(Book.id.in_(id_list)))
     return book

聚合查询

聚合计算:func.方法(模型类.属性)

  • count:统计行数量
  • avg:求平均值
  • max:求最大值
  • min:求最小值
  • sum:求和
js 复制代码
app.get("/book/count")
 async def get_count(db: AsyncSession = Depends(get_database)):
 # result = await db.execute(select(func.count(Book.id)))
 # result = await db.execute(select(func.max(Book.price)))
 # result = await db.execute(select(func.sum(Book.price)))
 result = await db.execute(select(func.avg(Book.price)))
 count = result.scalar()
 return count

分页查询

分页查询:select().offset().limit()

  • offset:跳过的记录数
  • limit:返回的记录数

!TIP

offset值 = (当前页码 - 1) * 每页数量limit

py 复制代码
@app.get("/book/get_books")
async def get_book_list(
    page: int = 1, 
    page_size: int = 3, 
    db: AsyncSession = Depends(get_database)
):
    skip = (page-1) * page_size
    result = await db.execute(select(Book).offset(skip).limit(page_size))
    books = result.scalars().all()
    return {"books": books}

联表查询

连表查询的前提是两张表在模型层面已经建立了关联。

假设我们有两个表:User(用户)和 Post(文章),一个用户可以有多篇文章(一对多):

python 复制代码
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

class Base(DeclarativeBase):
    pass

# 1. 用户模型 (父表)
class User(Base):
    __tablename__ = "users"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    
    # 【关键】定义关系:一个用户有多个 posts
    # back_populates 用于双向绑定,方便互相访问
    posts: Mapped[list["Post"]] = relationship(back_populates="owner")

# 2. 文章模型 (子表)
class Post(Base):
    __tablename__ = "posts"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(100))
    content: Mapped[str] = mapped_column(String)
    
    # 【关键】外键:指向 users 表的 id
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    
    # 【关键】定义关系:一篇文章属于一个 owner (User)
    owner: Mapped["User"] = relationship(back_populates="posts")

使用 .join()实现连表查询:

python 复制代码
@app.get("/users/who-wrote-python")
def get_users_who_wrote_python(db: Session = Depends(get_db)):
    # 显式 Join: 从 User 表 join Post 表
    stmt = (
        select(models.User)
        .join(models.Post)  # 默认是 INNER JOIN
        .where(models.Post.title.like("%Python%"))
        .distinct() # 防止一个用户写多篇 Python 文章导致结果重复
    )
    
    result = db.execute(stmt)
    users = result.scalars().all()
    
    return [{"name": u.name} for u in users]

新增操作

核心步骤:定义 ORM 对象 → 添加对象到事务:add(对象) → commit 提交到数据库

python 复制代码
app.post("/book/add_book")
async def add_book(book: BookBase, db: AsyncSession = Depends(get_database)):
    # 创建数据库模型对象
    book_obj = Book(**book.__dict__)	# 获取 book 参数,创建图书对象(__dict__ 返回 book 对象的属性字典)
    # book_obj = Book(**book.dict())	# 推荐使用 .dict() 方法	
    db.add(book_obj)
    await db.commit()
    return book
  • __dict__作用:

    python 复制代码
    # 假设传入的 book 参数是:
    # BookBase(title="Python编程", author="张三", price=99.0)
    
    print(book.__dict__)
    # 输出:{'title': 'Python编程', 'author': '张三', 'price': 99.0}
  • **解包操作:

    python 复制代码
    # 这行代码:
    book_obj = Book(**book.__dict__)
    
    # 等价于:
    book_obj = Book(
        title=book.title,
        author=book.author,
        price=book.price
    )
    # 或者:
    book_obj = Book(title="Python编程", author="张三", price=99.0)

缺省异常处理

python 复制代码
from sqlalchemy.exc import IntegrityError

@app.post("/book/add_book")
async def add_book(book: BookBase, db: AsyncSession = Depends(get_database)):
    try:
        book_obj = Book(**book.dict())
        db.add(book_obj)
        await db.commit()
        await db.refresh(book_obj)
        return book_obj
    #缺省异常
    except IntegrityError:
        await db.rollback()
        return {"error": "书籍添加失败,可能存在重复数据"}

完整实例代码:

python 复制代码
from pydantic import BaseModel
from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

# Pydantic 模型(用于请求体验证)
class BookBase(BaseModel):
 title: str
 author: str
 price: float

 class Config:
     schema_extra = {
         "example": {
             "title": "Python编程",
             "author": "张三",
             "price": 99.0
         }
     }

# SQLAlchemy 模型(用于数据库)
class Book(Base):
 __tablename__ = "books"

 id = Column(Integer, primary_key=True, index=True)
 title = Column(String, index=True)
 author = Column(String)
 price = Column(Float)

# FastAPI 端点
@app.post("/book/add_book")
async def add_book(book: BookBase, db: AsyncSession = Depends(get_database)):
 # 将 Pydantic 模型转换为 SQLAlchemy 模型
 book_obj = Book(**book.dict())  # 推荐使用 .dict() 而不是 .__dict__

 # 添加到数据库会话
 db.add(book_obj)

 # 提交事务到数据库
 await db.commit()

 # 刷新对象,获取数据库生成的值(如自增ID)
 await db.refresh(book_obj)

 # 返回添加的书籍信息
 return book_obj

更新操作

核心步骤:查询 get → 属性重新赋值 → commit 提交到数据库

python 复制代码
@app.put("/book/update_book/{book_id}")
async def update_book(book_id: int, data: BookUpdate, db: AsyncSession = Depends(get_database)):
    # 1. 查询
    book = await db.get(Book, book_id)
    if book is None:
        raise HTTPException(status_code=404, detail="数据不存在")
        # 2. 修改属性(重新赋值)
        book.bookname = data.bookname
        book.author = data.author
        book.price = data.price
        # 3. 提交
        await db.commit()
        return book

完整示例代码:

python 复制代码
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from typing import Optional

# 假设的模型定义
class BookUpdate(BaseModel):
 bookname: Optional[str] = None
 author: Optional[str] = None
 price: Optional[float] = None

@app.put("/book/update_book/{book_id}")
async def update_book(
 book_id: int, 
 data: BookUpdate, 
 db: AsyncSession = Depends(get_database)
):
 # 1. 查询要更新的书籍
 book = await db.get(Book, book_id)

 # 2. 检查书籍是否存在
 if book is None:
     raise HTTPException(status_code=404, detail=f"ID为{book_id}的书籍不存在")

 # 3. 修改属性(只更新提供的字段)
 if data.bookname is not None:
     book.bookname = data.bookname
 if data.author is not None:
     book.author = data.author
 if data.price is not None:
     book.price = data.price

 # 4. 提交事务
 await db.commit()

 # 5. 刷新对象获取最新状态(可选)
 await db.refresh(book)

 # 6. 返回更新后的书籍信息
 return book

删除操作

核心步骤:查询 get → delete 删除 → commit 提交到数据库

python 复制代码
@app.delete("/book/delete_book/{book_id}")
async def delete_book(book_id: int, db: AsyncSession = Depends(get_database)):
    # 先查询
    db_book = await db.get(Book, book_id)
    if db_book is None:
        	raise HTTPException(status_code=404, detail="Book not found")
            
    await db.delete(db_book)
    await db.commit()
    return {"message": "删除成功"}

完整示例代码:

python 复制代码
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError

@app.delete("/book/delete_book/{book_id}")
async def delete_book(
 book_id: int, 
 db: AsyncSession = Depends(get_database)
):
 # 1. 先查询要删除的书籍
 db_book = await db.get(Book, book_id)

 # 2. 检查书籍是否存在
 if db_book is None:
     raise HTTPException(
         status_code=404, 
         detail=f"ID为 {book_id} 的书籍不存在"
     )

 try:
     # 3. 删除书籍
     await db.delete(db_book)

     # 4. 提交事务
     await db.commit()

     # 5. 返回成功信息
     return {"message": f"ID为 {book_id} 的书籍已成功删除"}

 except SQLAlchemyError as e: # 捕获到的异常对象赋值给变量 e
     # 6. 错误处理
     await db.rollback()
     raise HTTPException(
         status_code=500, 
         detail=f"删除失败: {str(e)}"
     )

JWT

加密处理

安装依赖:

python 复制代码
# passlib[bcrypt]==1.7.4 官⽅⻓期稳定版本
pip install "passlib[bcrypt]==1.7.4"

创建加密上下文:

python 复制代码
from passlib.context import CryptContext

# 创建密码上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


# 密码加密
def get_hash_password(password: str):
    return pwd_context.hash(password)

生成访问令牌

Token:是服务器发给客户端的一段字符串,用来在后续请求中证明"你已经登录过了",作用:解决HTTP是五状态的问题,在每次请求中"自我证明身份"。

token在请求在是的位置是请求头

python 复制代码
Authorization: Bearer<token>

通过生成一个随机的 UUID 字符串作为 Token,并将其存储在数据库的 UserToken 表中,以此来管理用户的登录状态:

python 复制代码
# 生成Token
async def create_token(DB: AsyncSession, user_id: int):
    # 生成 token +设置过期时间 --> 查询数据库当前用户是否有 token --> 决定是否更新
    # 生成随机 token:
    token = str(uuid.uuid4())
    # 设置过期时间:
    expires_at = datetime.now() + timedelta(days=1)  # timedelta(days=?,hours=?,minutes=?,seconds=?)
    # 查询数据库:检查是否存在旧token
    res = await DB.execute(select(UserToken).where(UserToken.user_id == user_id))
    user_token = res.scalar_one_or_none()
    # 如果token存在就更新 token 和 过期时间,不存在就在数据库添加 token
    if user_token:
        user_token.token = token
        user_token.expires_at = expires_at
    else:
        user_token = UserToken(user_id=user_id, token=token, expires_at=expires_at)
        DB.add(user_token)
        await DB.commit()
    return token

统一响应格式

utils/中新增response.py定义通用响应格式方法success_response

完整代码:

python 复制代码
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder


def success_response(message: str = 'success', data=None):
    content = {
        "code": 200,
        "message": message,
        "data": data
    }

    # 把任何FastAPI、Pydantic、ORM对象都要正常响应-->code、message、data
    return JSONResponse(content=jsonable_encoder(content))

具体步骤:

先定义函数:

python 复制代码
def success_response(message: str = 'success', data=None):
  • message : 默认值为 'success'。用于向前端传达操作结果的简要描述(如 "用户创建成功"、"登录成功")。
  • data : 默认为 None。用于承载具体的业务数据(如用户信息列表、单个对象、ID 等)。

构建响应内容结构:

python 复制代码
    content = {
        "code": 200,
        "message": message,
        "data": data
    }

返回响应结果:

python 复制代码
    return JSONResponse(content=jsonable_encoder(content))
  • jsonable_encoder(content)
    • 假设 data 是一个 SQLAlchemy 的 User 对象,或者包含 datetime 字段。
    • 这个函数会递归遍历 content 字典,把所有非 JSON 原生类型的对象"清洗"成字典或基本类型。
    • 如果不加这一步 :直接 JSONResponse(content=content) 可能会在遇到 ORM 对象或时间对象时抛出 TypeError: Object of type ... is not JSON serializable
  • JSONResponse(...)
    • 将编码后的字典序列化为 JSON 字符串。
    • 生成一个 Starlette/FastAPI 兼容的响应对象。
    • 默认 HTTP 状态码为 200 OK

导入依赖说明:

python 复制代码
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
  • JSONResponse : FastAPI 提供的响应类。它不仅能返回字典,还能自动设置正确的 Content-Type: application/json 头,并允许你自定义 HTTP 状态码(虽然这里主要用它的序列化能力)。
  • jsonable_encoder : 这是核心工具 。FastAPI 内置的一个编码器,作用是将 Python 对象(包括 Pydantic 模型、SQLAlchemy ORM 对象、datetime 时间对象、Enum 枚举等)转换成标准的 JSON 兼容格式(如 dict、list、str、float)。

使用方法

python 复制代码
from utils.response import success_response

success_response(message="注册成功😄", data=responses_data)

异常处理

全局异常处理器

全局异常处理器(Global Exception Handler)是注册在 FastAPI 应用级别的异常处理函数,用于捕获业务层、数据

库层以及系统层抛出的异常,并以统一的响应格式返回给前端。

异常:

  • SQL 错误
  • 外键关联失败
  • 数据库连接异常
  • 提交事务失败

exception.py中编写异常处理:

python 复制代码
import traceback

from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from starlette import status

# 开发模式:返回详细错误信息
# 生产模式:返回简化错误信息
DEBUG_MODE = True  # 教学项目保持开启


# 处理 HTTPException 异常
async def http_exception_handler(request: Request, exc: HTTPException):
    # HTTPException 通常是业务逻辑主动抛出的,data 保持 None
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "code": exc.status_code,
            "message": exc.detail,
            "data": None
        }
    )


# 处理数据库完整性约束错误
async def integrity_error_handler(request: Request, exc: IntegrityError):
    error_msg = str(exc.orig)
    # 判断具体的约束错误类型
    if "username_UNIQUE" in error_msg or "Duplicate entry" in error_msg:
        detail = "用户名已存在"
    elif "FOREIGN KEY" in error_msg:
        detail = "关联数据不存在"
    else:
        detail = "数据约束冲突,请检查输入"

    # 开发模式下返回详细错误信息
    error_data = None
    if DEBUG_MODE:
        error_data = {
            "error_type": "IntegrityError",
            "error_detail": error_msg,
            "path": str(request.url)
        }

    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content={
            "code": 400,
            "message": detail,
            "data": error_data
        }
    )


# 处理 SQLAlchemy 数据库错误
async def sqlalchemy_error_handler(request: Request, exc: SQLAlchemyError):
    # 开发模式下返回详细错误信息
    error_data = None
    if DEBUG_MODE:
        error_data = {
            "error_type": type(exc).__name__,
            "error_detail": str(exc),
            # 格式化异常信息为字符串,方便日志记录和调试
            "traceback": traceback.format_exc(),
            "path": str(request.url)
        }

    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "code": 500,
            "message": "数据库操作失败,请稍后重试",
            "data": error_data
        }
    )


# 处理所有未捕获的异常
async def general_exception_handler(request: Request, exc: Exception):
    # 开发模式下返回详细错误信息
    error_data = None
    if DEBUG_MODE:
        error_data = {
            "error_type": type(exc).__name__,
            "error_detail": str(exc),
            # 格式化异常信息为字符串,方便日志记录和调试
            "traceback": traceback.format_exc(),
            "path": str(request.url)
        }

    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "code": 500,
            "message": "服务器内部错误",
            "data": error_data
        }
    )

exception_handler.py编写全局异常处理器:

python 复制代码
from fastapi import HTTPException
from sqlalchemy.exc import IntegrityError, SQLAlchemyError

from utils.exception import http_exception_handler, integrity_error_handler, sqlalchemy_error_handler, \
    general_exception_handler


# 注册全局异常处理
def register_exception_handler(app):
    # 子类在前,父类在后;具体在前,抽象在后
    app.add_exception_handler(HTTPException, http_exception_handler)  # 业务
    app.add_exception_handler(IntegrityError, integrity_error_handler)  # 数据完整性约束
    app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)  # 数据库
    app.add_exception_handler(Exception, general_exception_handler)  # 兜底

add_exception_handler 是什么?

  • 注册一个自定义的异常处理函数。当应用程序抛出特定类型的异常时,FastAPI 会调用你注册的 handler 函数来处理,而不是直接返回默认的 500 错误。

main.py中注册异常处理器:

python 复制代码
from utils.exception_handler import register_exception_handlers

# 注册异常处理器
register_exception_handlers(app)

数据库缓存

数据缓存:缓存是一种存储机制,用于临时存储数据或计算结果,当再次需要这些数据时,可以快速从缓存中检索,而不是重新

进行耗时或昂贵的获取和计算过程。

在网站开发中,缓存(Cache)是一个非常重要的概念,其核心作用是提高性能、降低延迟和减轻服务器负载。

主要优势:

  1. 提升性能和用户体验
  2. 减轻服务器/数据库负载
  3. 降低网络延迟
  4. 节省资源和成本

项目中缓存流程:

数据库缓存策略

最常见的策略:旁路策略

  1. :先查缓存,有数据则返回,没有数据则查询数据库
  2. : 更新数据库后,更新或删除缓存数据

!TIP

旁路缓存策略:是一种常见的缓存策略,其核心概念是应用程序主动管理缓存,在读取数据时先检查

缓存,如果缓存中没有数据,则从数据库或其他数据源加载数据,并将数据存入缓存;当数据更新或删除时,应用程

序也负责更新或删除缓存中的数据。

不同类型的数据,缓存时间不同,否则会出现缓存雪崩,数据越稳定,缓存越久;数据变化越快,缓存越短:

类型 时间
分类、配置 7200(2小时)
列表数据 600(10分钟)
详情数据 1800(30分钟)
验证码 120(2分钟)

Redis安装与配置

​ Redis 是一种高性能的 Key-Value 存储系统,它将数据存储在内存中,因此读写速度极快,非常适合作为应用层的

缓存服务。在 FastAPI 这样的后端框架中,通常在应用层使用像 Redis 这样的内存数据存储作为缓存。

安装Redis客户端:

python 复制代码
pip install redis

配置Redis客户端:

python 复制代码
import redis.asyncio as redis

REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0

# 创建Redis连接对象
redis_client = redis.Redis(
    # 配置参数:
    host=REDIS_HOST,
    port=REDIS_PORT,
    db=REDIS_DB,
    decode_responses=True
)

配置参数:

  • host:Redis服务器地址
  • port:端口号(默认:6379)
  • db:数据库编号(0~16)
  • decode_responses:是否讲返回的数据从字节流编码转化为字符串

Redis缓存方法

缓存操作就是围绕 Redis 做"存、取、删、判断、过期"等操作,让数据访问更快、数据库压力更小。

Redis 存储数据:key - value

Redis 缓存方法:

  • setex:设置缓存,并指定过期时间
    • key:str:缓存的键名(例如 "user:1001"
    • expire:int: 过期时间,单位为
    • value:str:要存储的值(通常是将对象序列化后的 JSON 字符串)
  • get:获取缓存值,若不存在,这返回None
    • key:str:要查询的键名
  • delete:删除指定的缓存键
    • key:str:要查询的键名
  • exists:检查缓存键是否存在,返回布尔值
    • key:str:要查询的键名

封装缓存操作方法

读取缓存
python 复制代码
# 获取缓存
async def Get_Cache(key: str):
    try:
        return await redis_client.get(key)
    except Exception as e:
        print(f"获取缓存失败: {e}")
        return None


# 获取JSON缓存
async def Get_Json_Cache(key: str):
    try:
        data = await redis_client.get(key)
        if data:
            return json.loads(data)  # 序列化,解析JSON字符串
        return None
    except Exception as e:
        print(f"获取 JSON 缓存失败: {e}")
        return None
设置缓存
python 复制代码
# 设置缓存
async def Set_Cache(key: str, value: Any, expire: int = 3600):
    try:
        # 检查是否为字典或列表
        if isinstance(value, (dict, list)):
            # 序列化:转字符串
            value = json.dumps(value, ensure_ascii=False)  # 保留中文字符
        await redis_client.setex(key, expire, value)
        return True
    except Exception as e:
        print(f"设置缓存失败: {e}")
        return False

!TIP

⚠️注意:添加Redis数据源中的驱动版本选择1.5

相关推荐
橙序员小站2 小时前
当所有人都在做 Agent,我想聊聊被遗忘的基础设施
后端·开源·aigc
小蚂蚁i2 小时前
LangChain 完全学习手册:看完就能上手
后端·python·ai编程
Memory_荒年2 小时前
TiDB:当 MySQL 遇上分布式,生了个“超级混血儿”
java·数据库·后端
一步一个脚印一个坑2 小时前
用 APM 全链路追踪,29ms 内定位到 Docker 部署的 SSL 配置错误
javascript·后端·监控
aircrushin2 小时前
端到端AI决策架构如何重塑实时协作体验?
前端·javascript·后端
苦瓜小生2 小时前
【黑马点评学习笔记 | 实战篇 】| 6-Redis消息队列
redis·笔记·后端
Aawy1202 小时前
Python生成器(Generator)与Yield关键字:惰性求值之美
jvm·数据库·python
爱吃的小肥羊3 小时前
ChatGPT、Claude、Gemini,到底该给谁交钱?这是我的深度测评
aigc·openai·ai编程
沐硕3 小时前
《基于改进协同过滤与多目标优化的健康饮食推荐系统设计与实现》
java·python·算法·fastapi·多目标优化·饮食推荐·改进协同过滤