超级小牙医
126.69M · 2026-03-22
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,专为在 Python 中构建 RESTful API 而设计。
FastAPI 特点
FastAPI 适用场景
FastAPI 依赖 Python 3.8 及更高版本。
检查当前python版本:
# 检查 Python 版本
python --version
# 或者
python3 --version
安装 FastAPI 很简单,这里我们使用 pip 命令来安装。
pip install fastapi
另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:
pip install "uvicorn[standard]"
创建一个名为 main.py 的文件,添加以下代码:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
在命令行中运行以下命令以启动应用:
uvicorn main:app --reload
现在,打开浏览器并访问 ****,你应该能够看到 FastAPI 自动生成的交互式文档,并在根路径 ("/") 返回的 JSON 响应。
除了通过依赖下载之外,我们还可以用Pycharm集成直接创建项目:
FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。
这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。
通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率
在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。
默认情况下,你可以通过访问 来打开 Swagger UI 风格的文档,或者通过 来打开 ReDoc 风格的文档。
Swagger UI:ReDoc:创建 FastAPI 实例和根路径路由:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def Hello():
return {"Hello": "World"}
代码说明:
FastAPI():创建 FastAPI 应用实例。@app.get("/"):使用 @app.get 装饰器创建一个处理根路径的路由。def Hello():路由处理函数,返回一个包含 {"Hello": "World"} 的字典。设置路由参数:
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。启动应用和测试路由:
访问 查看根路径的响应:
{
"Hello": "World"
}
访问 查看带路径参数和查询参数的响应:
{
"id": 2,
"query": "ABC"
}
/app/{id}GET?id=1&key=2GET消息体body中POST、PUT在 FastAPI 中,Path 是一个用于定义和验证路径参数(Path Parameters)的函数。它属于 fastapi 模块,专门用于从 URL 路径中提取变量,并对其进行类型检查、数据验证和文档生成。
使用 Path 可以提供更强大的功能:
gt, ge)、最大值 (lt, le)、正则表达式 (regex)、长度限制等。Path(...) 来显式声明该参数是必需的(尽管在现代 FastAPI 版本中,仅靠类型注解通常也能工作,但使用 Path 是最佳实践,特别是需要验证时)。基本用法:
作为类型提示的替代或者补充:如果你只需要类型检查,不需要额外的验证或者文档描述,可以直接使用类型注解。写作Path(...)
@app.get("/{id}")
def GetId(id: int = Path(..., title="物品ID", description="物品ID的描述")):
return {"id": id}
添加数据验证:可以限制数值的范围或者字符串的格式:
@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 )字符串验证:对于字符串类型的路径参数,可以使用regex进行格式正则表达式校验
@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}
现代写法:推荐使用typing.Annotated将类型和校验逻辑分开,这样代码更加清晰,也方便复用验证逻辑:
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 |
在 FastAPI 中,Query 是用于定义和验证查询参数(Query Parameters)的函数。查询参数是 URL 中 ? 后面的键值对,例如 /items/?skip=0&limit=10 中的 skip 和 limit。
与 Path 类似,Query 允许你:
int, float, bool, list 等)。基本用法:
可选参数:如果给参数赋予了默认值(例如None或者具体数字),FastAPI会自动将其识别为可选的查询参数
@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
{
"id": 1,
"info": {
"skip": 10,
"limit": 1000
}
}
必需参数:如果查询参数出必需的,你需要将默认值设置为...:
int = Query(..., description="跳过的物品数量")
高级验证:Query支持所有 Pydantic 的验证规则。
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}
列表类型的查询参数:可以自动处理重复的查询参数,将其转换成列表:
@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),或者使用逗号分隔(取决于客户端和配置,默认是多次键)。
{
"id": 1,
"list": [2, 3, 4]
}
现代写法:同样将类型信息与验证逻辑分离,使用typing.Annotated
# 定义验证逻辑
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
}
在 FastAPI 中,Field 是用于定义 Pydantic 模型(数据模型)内部字段 的验证规则和元数据的函数。
它与 Path 和 Query 的核心区别在于作用域:
Path / Query / Body / Header / Cookie:用于定义 API 接口函数参数 的来源和验证。Field:用于定义 Pydantic 模型类内部属性 的验证规则和文档信息。当你定义一个继承自 BaseModel 的类(用于请求体 Request Body 或响应体 Response Body)时,你需要使用 Field 来告诉 Pydantic/FastAPI 如何验证该字段的数据,以及如何在 API 文档中展示它。
Field 通常作为类型注解的默认值使用。
接下来创建了一个 /items/ 路由,使用 @app.post 装饰器表示这是一个处理 POST 请求的路由:
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_ofmin_length, max_length, regex (或 pattern)min_items, max_items (在 Pydantic V2 中已改为 min_length, max_length)title, description, example, examples, deprecatedfrom 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-zd]{8,}$", # 简单示例:必须包含字母和数字
description="密码至少8位,包含字母和数字"
)
这是 Field 的一个非常强大的功能。它允许 Python 变量名与 JSON 数据中的键名(如 userName 或 user-name)不一致。
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(可选)
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 将类型定义与验证逻辑分离。这使得代码更清晰,且方便复用验证规则。
现代写法:
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 | 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)主要有两种方式:函数返回类型注解和路径操作装饰器参数。
注解:直接在 def 或 async def 后面使用 -> 指定返回类型
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}
response_model 参数:在路径操作装饰器(如 @app.get)中显式传递 response_model 参数
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}
]
路由处理函数返回一个字典,该字典将被 FastAPI 自动转换为 JSON 格式,并作为响应发送给客户端:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
路由处理函数返回一个 Pydantic 模型实例,FastAPI 将自动将其转换为 JSON 格式,并作为响应发送给客户端:
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 端点的输出格式。这一机制在提供自动数据验证和序列化的同时,更是保障数据安全性的第一道防线。
使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据。
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_id 为 42 会返回 404 状态码:
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 实现重定向,将客户端重定向到 /items/ 路由。
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}
以上代码在浏览器访问 会自动跳转到 页面
如果一个接口可能返回多种状态码(如 200 成功,404 未找到),且它们的响应结构不同,可以使用 responses 参数。
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 自定义响应头:
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)
以上代码在浏览器访问 页面显示如下,可以看到我们自定义的响应头
FastAPI 提供了路径操作依赖项的机制,允许你在路由处理函数执行之前或之后运行一些额外的逻辑,依赖项就是一个函数,且可以使用与路径操作函数相同的参数。路径操作依赖项提供了一种灵活的方式来组织代码、验证输入、进行身份验证等。
依赖项:是在路由操作函数执行前或后运行的可复用的函数或对象。
它们被用于执行一些通用的逻辑,如验证、身份验证、数据库连接等。在 FastAPI 中,依赖项通常用于两个方面:
在 FastAPI 中,通过在路由操作函数参数中声明依赖项来实现依赖注入。
使用方法:
定义依赖项
# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
使用依赖项
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 函数。
请求路径:
# 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),我们使用了这个后处理依赖项,实现了在路由执行后进行额外操作的功能。
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_parameters 和 verify_token 是两个不同的依赖项函数,verify_token 依赖于 common_parameters,这种组合依赖项的方式允许我们在路由执行前先验证一些参数,然后在进行身份验证。
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:
pip install python-multipart
添加代码:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}
接下来我们可以进入 API 文档 进行测验。
使用 Pydantic 模型来声明表单数据模型。
在模型中,使用 Field 类型声明每个表单字段,并添加必要的验证规则。
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}
在路由操作函数中,可以使用 Form 类型来接收表单数据。
Form 类型的参数可以与 Pydantic 模型的字段一一对应,以实现表单数据的验证和转换。
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 文档 进行测验。
如果表单包含文件上传,可以使用 UploadFile 类型处理。
以下是一个处理文件上传的实例:
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 构建。
ASGI是 Python 异步 Web 服务器和应用程序之间的标准接口。
# 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 中的异步路径操作:
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": "异步响应"}
REST是一种 Web API 设计风格,强调:
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)
常用状态码速查:
# 成功状态码
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 # 服务不可用
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
)
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:主入口文件
创建模块化目录结构:
app/
---routers/
------路由1/
------路由2/
编写独立路由模块:
rom fastapi import APIRouter
# 创建 APIRouter 实例
router = APIRouter(prefix="/api/news",tags=["news"])
@router.get("/categories")
async def get_categories():
return {"msg": "获取分类成功"
prefix:路由前缀tags:交互式文档分组在主入口文件中注册路由:
from fastapi import FastAPI
from routers import news
app = FastAPI()
# 注册路由
app.include_router(news.router)
中间件(Middleware)是一个在每次请求进入 FastAPI 应用时都会被执行的函数。 它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次。
常用的中间件处理情况:
基本使用:
函数的顶部使用装饰器 @app.middleware("http")
语法格式:
@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 返回的那个,或者是基于它修改后的对象)。| 特性 | 中间件 (Middleware) | 依赖注入 (Depends) |
|---|---|---|
| 作用范围 | 全局。对应用接收到的每一个请求生效(除非你在代码里手动写 if 排除某些路径)。 | 局部。仅对声明了该依赖的特定路径操作函数(或包含该依赖的其他依赖)生效。 |
| 操作对象 | Starlette/FastAPI 的 Request 和 Response 对象。操作的是底层的 HTTP 协议数据(Header, Body, Status Code)。 | 普通的 Python 对象。你可以返回任何类型(dict, class instance, DB session, str 等)。 |
| 执行时机 | 在路由匹配之前就开始执行(请求阶段),在响应返回给客户端之前最后执行(响应阶段)。 | 在路由匹配之后,实际的路径操作函数执行之前。 |
| 主要用途 | 跨切面基础设施: • CORS / Gzip • 全局请求/响应日志 • 简单的 IP 拦截 • 修改全局 Header | 业务逻辑复用: • 数据库连接管理 • 用户身份验证与授权(获取 current_user) • 参数验证与转换 • |
跨域资源共享(CORS)是一种浏览器安全机制,用于允许运行在一个源(Origin)的 Web 应用,通过浏览器向另一 个源的服务器发起跨域 HTTP 请求,并在服务器授权的前提下获取资源。
解决办法:CORS中间件,让后端主动告诉浏览器:这个前端“允许访问”
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(Object-RelationalMapping,对象关系映射)是一种编程技术,用于在面向对象编程语言和关系型数据库之间 建立映射。它允许开发者通过操作对象的方式与数据库进行交互,而无需直接编写复杂的SQL语句。
优势:
# 安装 SQLAlchemy + asyncio 驱动
pip install sqlalchemy[asyncio] aiomysql
(核心库):这是 Python 最著名的 SQL 工具包和 ORM(对象关系映射)框架asyncio:开启异步支持
aiomysql:是一个第三方的、纯 Python 编写的 MySQL 驱动程序
# 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(包含通用属性和字段的映射)
from sqlalchemy.orm import DeclarativeBase
# 定义模型类:基类
class Base(DeclarativeBase):
pass
使用 SQLAlchemy 2.0 的 Mapped 和 mapped_column 语法,这是类型安全的新标准。
语法格式:
列名: Mapped[T] = mapped_column(列属性)
1. Mapped[T]:
T”。id: Mapped[int] 表示 id 属性在 Python 代码中是一个整数。2. mapped_column():
Column()。mapped_column(Integer, primary_key=True) 告诉 SQLAlchemy 在数据库中创建一个整数类型的主键列。mapped_column 的参数非常灵活,主要分为三类:
Integer, String)。primary_key=True: 主键。unique=True: 唯一约束。nullable=False: 非空约束 (NOT NULL)。index=True: 创建索引。ForeignKey(...): 外键约束。default=value: Python 端默认值。当你执行 user = User() 且没传该字段时生效。如果直接执行 SQL 插入,此值不生效。创建工厂函数:
创建异步 Session 工厂
# class_=AsyncSession: 指定使用异步会话
AsyncSessionLocal = async_sessionmaker(
bind=DB_ENGINE, # 绑定数据库引擎
class_=AsyncSession, # 指定会话类
expire_on_commit=False # 会话对象不过期,不重新查询数据库
)
添加依赖项,用于获取依赖会话
依赖注入函数 (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() # 关闭会话
路由中使用:
@app.get("/book/books")
async def get_book_list(
db: AsyncSession = Depends(Get_Database)
):
# 数据库操作
pass
通常我们会在项目的config目录下新建一个db.py作为数据库orm配置文件,可以直接拷贝以下代码:
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():
@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():
@app.get("/book/get_book")
async def get_book(db: AsyncSession=Depends(get_database)):
book=result.scalars().first()
return book
get(模型类, 主键值):
@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, ...)
条件:
比较判断:==; >; <; >=; <= 等
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
模糊查询:like()
app.get("/book/get_books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
book = result.scalars().all()
return book
%:零个、一个或多个字符:_:一个单个字符与非查询:&; | ; ~
@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_()
@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.方法(模型类.属性)
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:返回的记录数@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(文章),一个用户可以有多篇文章(一对多):
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()实现连表查询:
@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 提交到数据库
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__作用:
# 假设传入的 book 参数是:
# BookBase(title="Python编程", author="张三", price=99.0)
print(book.__dict__)
# 输出:{'title': 'Python编程', 'author': '张三', 'price': 99.0}
**解包操作:
# 这行代码:
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)
缺省异常处理
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": "书籍添加失败,可能存在重复数据"}
核心步骤:查询 get → 属性重新赋值 → commit 提交到数据库
@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
核心步骤:查询 get → delete 删除 → commit 提交到数据库
@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": "删除成功"}
安装依赖:
# passlib[bcrypt]==1.7.4 官⽅⻓期稳定版本
pip install "passlib[bcrypt]==1.7.4"
创建加密上下文:
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在请求在是的位置是请求头:
Authorization: Bearer<token>
通过生成一个随机的 UUID 字符串作为 Token,并将其存储在数据库的 UserToken 表中,以此来管理用户的登录状态:
# 生成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:
完整代码:
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))
具体步骤:
先定义函数:
def success_response(message: str = 'success', data=None):
message: 默认值为 'success'。用于向前端传达操作结果的简要描述(如 "用户创建成功"、"登录成功")。data: 默认为 None。用于承载具体的业务数据(如用户信息列表、单个对象、ID 等)。构建响应内容结构:
content = {
"code": 200,
"message": message,
"data": data
}
返回响应结果:
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(...):
使用方法:
from utils.response import success_response
success_response(message="注册成功", data=responses_data)
全局异常处理器(Global Exception Handler)是注册在 FastAPI 应用级别的异常处理函数,用于捕获业务层、数据
库层以及系统层抛出的异常,并以统一的响应格式返回给前端。
异常:
在exception.py中编写异常处理:
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编写全局异常处理器:
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) # 兜底
在main.py中注册异常处理器:
from utils.exception_handler import register_exception_handlers
# 注册异常处理器
register_exception_handlers(app)
数据缓存:缓存是一种存储机制,用于临时存储数据或计算结果,当再次需要这些数据时,可以快速从缓存中检索,而不是重新
进行耗时或昂贵的获取和计算过程。
在网站开发中,缓存(Cache)是一个非常重要的概念,其核心作用是提高性能、降低延迟和减轻服务器负载。
主要优势:
项目中缓存流程:
最常见的策略:旁路策略
不同类型的数据,缓存时间不同,否则会出现缓存雪崩,数据越稳定,缓存越久;数据变化越快,缓存越短:
| 类型 | 时间 |
|---|---|
| 分类、配置 | 7200(2小时) |
| 列表数据 | 600(10分钟) |
| 详情数据 | 1800(30分钟) |
| 验证码 | 120(2分钟) |
Redis 是一种高性能的 Key-Value 存储系统,它将数据存储在内存中,因此读写速度极快,非常适合作为应用层的
缓存服务。在 FastAPI 这样的后端框架中,通常在应用层使用像 Redis 这样的内存数据存储作为缓存。
安装Redis客户端:
pip install redis
配置Redis客户端:
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 存储数据:key - value
Redis 缓存方法:
setex:设置缓存,并指定过期时间
key:str:缓存的键名(例如 "user:1001")expire:int: 过期时间,单位为秒value:str:要存储的值(通常是将对象序列化后的 JSON 字符串)get:获取缓存值,若不存在,这返回None
key:str:要查询的键名delete:删除指定的缓存键
key:str:要查询的键名exists:检查缓存键是否存在,返回布尔值
key:str:要查询的键名# 获取缓存
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
# 设置缓存
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
LLM大模型会话ID身份跟踪标识原理解构:从模型无状态下的会话ID(Session ID)原理分析以及自主实现会话跟踪
突破 Serverless 无状态限制:用 AgentRun 破解 Agent 沙箱工程化挑战
2026-03-22
2026-03-22