FastAPI学习博客(二):核心功能详解(路由、请求、响应、中间件)
前言
在上一篇博客中,我们完成了FastAPI的环境搭建,并写出了第一个Hello World程序。今天,我们将深入学习FastAPI的核心功能,这些功能是你开发实际项目时必须掌握的内容。
本文会尽量避免复杂的代码堆砌,用最清晰的方式讲解每一个概念。请放心,即使你没有任何Web开发经验,也能轻松跟上。
一、路由系统:告别单文件噩梦
什么是路由?
简单来说,路由就是“网址”和“处理函数”之间的对应关系。
举个例子:
-
当用户访问
http://localhost:8000/users/123时,FastAPI需要知道应该调用哪个函数来处理这个请求 -
当用户访问
http://localhost:8000/products/456时,又应该调用另一个函数
这个映射关系,就是路由。
为什么需要路由模块化?
很多初学者会把所有路由写在一个main.py文件里。这在只有两三个接口时没问题,但一旦项目变大,这个文件会变得非常臃肿,难以维护。
想象一下,一个电商系统可能有:用户模块、商品模块、订单模块、购物车模块、支付模块...如果全部塞进一个文件,代码可能达到几千行。无论是找代码还是改代码,都会非常痛苦。
模块化的好处:
-
每个模块独立一个文件,职责清晰
-
多人协作时不会产生代码冲突
-
方便复用和测试
-
便于实现API版本管理
如何创建路由模块?
在FastAPI中,我们使用APIRouter来创建独立的路由模块。
首先,创建一个routers文件夹,然后在里面新建users.py文件:
# routers/users.py
from fastapi import APIRouter
# 创建路由器实例
# prefix:所有路由的统一前缀,避免重复写"/users"
# tags:在Swagger文档中分组显示
router = APIRouter(prefix="/users", tags=["用户管理"])
@router.get("/")
def get_all_users():
"""获取所有用户"""
return [{"id": 1, "name": "张三"}, {"id": 2, "name": "李四"}]
@router.get("/{user_id}")
def get_user(user_id: int):
"""根据ID获取单个用户"""
return {"id": user_id, "name": f"用户{user_id}"}
@router.post("/")
def create_user(name: str, age: int):
"""创建新用户"""
return {"message": f"用户{name}创建成功", "age": age}
同样的方式,我们可以创建商品模块:
# routers/products.py
from fastapi import APIRouter
router = APIRouter(prefix="/products", tags=["商品管理"])
@router.get("/")
def get_all_products():
return [{"id": 1, "name": "手机"}, {"id": 2, "name": "电脑"}]
@router.get("/{product_id}")
def get_product(product_id: int):
return {"id": product_id, "name": f"商品{product_id}"}
如何在主文件中注册路由?
创建好各个模块后,我们需要在main.py中把它们注册到FastAPI应用上:
# main.py
from fastapi import FastAPI
from routers import users, products
# 创建FastAPI应用
app = FastAPI(title="我的API项目")
# 注册路由模块
app.include_router(users.router)
app.include_router(products.router)
@app.get("/")
def root():
return {"message": "欢迎访问我的API"}
代码非常简洁。现在,我们的项目结构清晰多了:
-
main.py:只负责创建应用和注册路由 -
routers/users.py:所有用户相关的接口 -
routers/products.py:所有商品相关的接口
关于prefix和tags的说明
上面的代码中,你可能注意到了两个参数:
prefix(路由前缀):在路由模块中设置prefix="/users"后,模块内所有路由都会自动加上这个前缀。比如@router.get("/")实际对应的路径是/users/,@router.get("/{user_id}")对应的路径是/users/{user_id}。这样做的好处是避免在每个函数上重复写/users。
tags(标签):这个参数不影响功能,只影响Swagger文档的显示。设置tags=["用户管理"]后,所有用户相关的接口会在文档中归为一组,方便查看。
二、请求参数:客户端如何传递数据?
在Web开发中,客户端(如浏览器、手机App)需要向服务器传递各种数据。FastAPI提供了多种方式来接收这些数据,我们逐一来看。
1. 路径参数
路径参数是URL路径的一部分,通常用来标识某个具体资源。
什么是路径参数?
看这个URL:http://localhost:8000/users/123
其中的123就是路径参数,它代表用户的ID。这种设计非常直观,符合RESTful API的规范。
如何使用?
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
# user_id会自动从URL中提取,并转换为int类型
return {"user_id": user_id, "name": f"用户{user_id}"}
当用户访问/users/123时,user_id的值就是123。
类型转换的优势:如果你声明user_id: int,FastAPI会自动将字符串"123"转换为整数123。如果用户传入了无法转换的值(如/users/abc),FastAPI会自动返回清晰的错误信息,不需要你写任何校验代码。
多个路径参数:
@app.get("/users/{user_id}/orders/{order_id}")
def get_order(user_id: int, order_id: int):
return {"user_id": user_id, "order_id": order_id}
访问/users/10/orders/99即可同时获取两个参数。
2. 查询参数
查询参数是URL中?后面的部分,通常用来传递筛选条件或配置选项。
什么是查询参数?
看这个URL:http://localhost:8000/users?page=1&size=10&keyword=张三
page=1、size=10、keyword=张三就是查询参数。它们的特点是:
-
以
?开头 -
多个参数用
&连接 -
格式为
key=value -
顺序不重要
-
可以省略(可选)
如何使用?
@app.get("/users")
def get_users(page: int = 1, size: int = 10, keyword: str = None):
return {
"page": page,
"size": size,
"keyword": keyword,
"data": [] # 实际项目中这里会是查询结果
}
这里的page、size、keyword就是查询参数。注意它们是在函数参数中直接定义的,没有使用任何特殊语法。
必填和可选:
-
如果参数有默认值(如
page: int = 1),它就是可选的 -
如果没有默认值(如
keyword: str),它就是必填的
访问/users?page=2&size=20&keyword=李四,FastAPI会自动提取这三个值并传入函数。
路径参数和查询参数的区别:
| 特性 | 路径参数 | 查询参数 |
|---|---|---|
| 位置 | URL路径中 | URL中?后面 |
| 示例 | /users/123 |
/users?page=1 |
| 用途 | 标识具体资源 | 筛选、排序、分页 |
| 是否必须 | 通常是 | 通常可选 |
3. 请求体(Request Body)
当客户端需要向服务器提交大量数据(如创建用户、发布文章)时,通常会使用请求体。请求体放在HTTP请求的Body中,不像URL有长度限制。
为什么需要请求体?
查询参数适合传递少量简单数据,但如果你要提交一个包含用户名、邮箱、密码、年龄、地址等多个字段的用户信息,放在URL里显然不合适。这时候就需要使用请求体。
如何使用Pydantic模型?
FastAPI使用Pydantic来定义和验证请求体的数据结构:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# 定义请求体的数据结构
class UserCreate(BaseModel):
name: str
email: str
age: int
password: str
# 使用请求体
@app.post("/users")
def create_user(user: UserCreate):
# FastAPI会自动:
# 1. 读取请求体中的JSON数据
# 2. 验证每个字段的类型是否正确
# 3. 将数据封装成UserCreate对象
return {
"message": f"用户{user.name}创建成功",
"email": user.email,
"age": user.age
}
Pydantic的优势:
你不需要手动解析JSON、不需要写各种校验代码。如果客户端发送的数据格式不对(比如age传了字符串"十八"而不是数字),FastAPI会自动返回清晰的错误信息。
4. 表单数据
表单数据也是放在请求体中的,但格式不同于JSON。传统的HTML表单使用的是这种格式。
什么时候用表单?
当你需要处理传统的HTML表单提交,或者对接一些老旧的系统时,可能需要用到表单数据。
如何使用?
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login")
def login(username: str = Form(...), password: str = Form(...)):
# Form(...) 表示这个参数来自表单数据,且是必填的
return {"username": username, "message": "登录成功"}
注意两点:
-
需要使用
Form(...)而不是直接写类型 -
需要安装
python-multipart库:pip install python-multipart
5. 文件上传
FastAPI对文件上传提供了非常好的支持。
单文件上传:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload")
def upload_file(file: UploadFile = File(...)):
# file.filename:原始文件名
# file.file:文件内容
return {"filename": file.filename, "size": len(file.file.read())}
多文件上传:
from typing import List
@app.post("/uploads")
def upload_files(files: List[UploadFile] = File(...)):
return [f.filename for f in files]
参数类型总结
| 参数类型 | 使用场景 | 写法示例 |
|---|---|---|
| 路径参数 | 资源标识 | @app.get("/{id}") |
| 查询参数 | 筛选分页 | def get(page: int = 1) |
| 请求体 | 提交数据 | def create(user: UserModel) |
| 表单 | 传统表单 | def login(user: str = Form(...)) |
| 文件 | 上传文件 | def upload(file: UploadFile) |
三、响应对象:服务器如何返回数据?
默认响应
FastAPI默认会自动将你返回的字典转换为JSON格式。这是最常用也是最简单的方式:
@app.get("/user")
def get_user():
# 直接返回字典,自动转为JSON
return {"id": 1, "name": "张三", "age": 25}
常见的响应类型
除了默认的JSON,FastAPI还支持返回其他格式:
1. HTML响应
当你的API需要返回一个完整的HTML页面(而不是数据)时使用:
from fastapi.responses import HTMLResponse
@app.get("/page", response_class=HTMLResponse)
def get_html():
return "<h1>Hello FastAPI</h1><p>这是一个HTML页面</p>"
2. 重定向
当你想把用户从一个地址跳转到另一个地址时使用:
from fastapi.responses import RedirectResponse
@app.get("/old")
def redirect():
# 访问/old会自动跳转到/docs
return RedirectResponse("/docs")
3. 文件下载
当你的API需要提供文件下载功能时:
from fastapi.responses import FileResponse
@app.get("/download")
def download():
# 返回服务器上的文件,浏览器会提示下载
return FileResponse("files/report.pdf", filename="报告.pdf")
响应模型(Response Model)
什么是响应模型?
响应模型用来控制哪些数据应该返回给客户端。这在处理敏感信息时特别有用。
一个实际例子:
假设你的数据库中有用户表,存储了id、name、email、password_hash(密码哈希)。当你查询用户信息时,绝对不应该把password_hash返回给客户端。
使用响应模型可以轻松解决这个问题:
from pydantic import BaseModel
# 定义响应模型(只包含需要返回的字段)
class UserResponse(BaseModel):
id: int
name: str
email: str
# 注意:没有password_hash字段
# 数据库中的完整用户数据
class UserInDB(BaseModel):
id: int
name: str
email: str
password_hash: str # 敏感字段
@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int):
# 从数据库查询,得到完整数据(包含密码)
db_user = UserInDB(id=1, name="张三", email="zhang@example.com", password_hash="abc123")
# 返回时自动过滤掉password_hash,只返回UserResponse中定义的字段
return db_user
使用response_model的好处是:你不需要手动删除敏感字段,FastAPI会自动处理。
状态码(Status Code)
HTTP状态码用来告诉客户端请求的结果。常见的有:
-
200:成功(GET请求默认)
-
201:创建成功(通常用于POST请求)
-
204:成功但无内容(通常用于DELETE请求)
-
400:客户端错误(如参数不对)
-
401:未认证
-
403:无权限
-
404:资源不存在
-
500:服务器内部错误
指定状态码的方式:
@app.post("/users", status_code=201) # 创建成功返回201
def create_user():
return {"message": "用户创建成功"}
@app.delete("/users/{user_id}", status_code=204) # 删除成功无内容返回
def delete_user(user_id: int):
# 删除逻辑
return None # 204响应不应该有body
四、中间件:请求和响应的拦截器
什么是中间件?
中间件是一个在请求到达你的路由函数之前、以及响应返回给客户端之后,执行一些代码的组件。
可以把中间件想象成一个安检通道:
-
请求进来时,先经过安检(中间件)
-
安检通过后,才进入业务处理(路由函数)
-
业务处理完成后,响应出去之前,还要再经过一次安检
中间件能做什么?
中间件的应用场景非常多:
-
记录每个请求的日志
-
统计接口响应时间
-
添加统一的响应头(如
X-Powered-By) -
请求限流(防止恶意请求)
-
全局异常处理
-
CORS跨域处理
-
Gzip压缩响应内容
如何编写中间件?
下面是一个记录请求日志和响应时间的中间件:
from fastapi import FastAPI, Request
import time
app = FastAPI()
@app.middleware("http")
async def log_middleware(request: Request, call_next):
# 这部分代码在请求进入路由前执行
start_time = time.time()
print(f"收到请求: {request.method} {request.url.path}")
# 调用下一个中间件或最终的路由函数
response = await call_next(request)
# 这部分代码在响应返回给客户端前执行
process_time = time.time() - start_time
print(f"处理耗时: {process_time:.4f}秒")
# 可以修改响应头
response.headers["X-Process-Time"] = str(process_time)
return response
代码解释:
-
@app.middleware("http"):声明这是一个HTTP中间件 -
request:请求对象,可以从中获取URL、请求头、客户端IP等信息 -
call_next:下一个中间件或路由函数 -
await call_next(request):执行后续处理,返回响应对象 -
可以在
call_next前后分别添加请求前和响应后的逻辑
内置中间件
FastAPI(实际是Starlette)提供了多个实用的内置中间件:
CORS中间件(最常用):
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 允许的前端地址
allow_credentials=True,
allow_methods=["*"], # 允许所有HTTP方法
allow_headers=["*"], # 允许所有请求头
)
为什么需要CORS?因为浏览器有安全策略,默认不允许网页向不同域名的服务器发请求。如果你的前端和后端不在同一个域名下,就需要配置CORS中间件。
GZip中间件(压缩响应):
from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)
# 响应大于1KB时自动压缩,可以显著减少传输流量
中间件的执行顺序
如果你添加了多个中间件,它们的执行顺序很重要:
请求 → 中间件1(前) → 中间件2(前) → 路由函数 → 中间件2(后) → 中间件1(后) → 响应
简单说:请求阶段从上往下执行,响应阶段从下往上执行。
五、综合示例:一个简单的用户管理系统
把今天学到的知识结合起来,我们写一个简单的用户管理API:
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
app = FastAPI(title="用户管理系统")
# 定义数据模型
class User(BaseModel):
id: int
name: str
email: str
age: int
class UserCreate(BaseModel):
name: str
email: str
age: int
# 模拟数据库
fake_db = []
current_id = 1
# 获取所有用户
@app.get("/users", response_model=List[User])
def get_users():
return fake_db
# 获取单个用户
@app.get("/users/{user_id}", response_model=User)
def get_user(user_id: int):
for user in fake_db:
if user.id == user_id:
return user
raise HTTPException(status_code=404, detail="用户不存在")
# 创建用户
@app.post("/users", status_code=201, response_model=User)
def create_user(user_data: UserCreate):
global current_id
new_user = User(id=current_id, **user_data.dict())
fake_db.append(new_user)
current_id += 1
return new_user
# 删除用户
@app.delete("/users/{user_id}", status_code=204)
def delete_user(user_id: int):
for i, user in enumerate(fake_db):
if user.id == user_id:
fake_db.pop(i)
return
raise HTTPException(status_code=404, detail="用户不存在")
启动服务后:
-
访问
/docs可以看到自动生成的API文档 -
可以通过POST接口创建用户
-
可以通过GET接口查询用户
-
可以通过DELETE接口删除用户
六、总结
今天的内容比较多,我们回顾一下核心要点:
1. 路由模块化
-
使用
APIRouter把不同模块的接口分开 -
用
prefix统一路由前缀 -
用
tags在文档中分组
2. 请求参数
-
路径参数:URL中的变量,如
/users/{id} -
查询参数:
?后面的键值对 -
请求体:使用Pydantic模型,适合提交复杂数据
-
表单和文件上传:特殊场景使用
3. 响应对象
-
默认返回字典自动转JSON
-
可用
response_model过滤敏感字段 -
用
status_code指定HTTP状态码
4. 中间件
-
在请求前后执行通用逻辑
-
常用于日志、计时、CORS、压缩等
掌握了这些核心功能,你已经可以开发大部分Web API了。下一篇博客我们会深入讲解数据库操作和依赖注入,敬请期待!
练习题
创建一个商品管理模块,包含商品的新增、查询、删除功能
为你的API添加一个日志中间件,记录每个请求的路径和处理时间
尝试使用响应模型,确保删除用户时只返回状态码,不返回内容
有任何问题,欢迎在评论区留言讨论!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)