Appearance
26|FastAPI 路由与 HTTP 方法
一个接口由 URL 路径和 HTTP 方法共同确定。同一个 /articles 路径,GET 是查询列表,POST 是创建文章,PUT 是更新,DELETE 是删除。FastAPI 用装饰器把函数注册为路由处理器,这篇把路由注册、路径分组、HTTP 方法覆盖完整。
一、HTTP 方法装饰器
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/articles") # 查询列表
async def list_articles():
return [{"id": 1, "title": "入门"}]
@app.get("/articles/{id}") # 查询单条
async def get_article(id: int):
return {"id": id, "title": "入门"}
@app.post("/articles") # 创建
async def create_article():
return {"id": 2, "title": "新文章"}
@app.put("/articles/{id}") # 全量更新
async def update_article(id: int):
return {"id": id, "title": "已更新"}
@app.patch("/articles/{id}") # 部分更新
async def patch_article(id: int):
return {"id": id, "title": "部分更新"}
@app.delete("/articles/{id}") # 删除
async def delete_article(id: int):
return {"deleted": id}| 装饰器 | HTTP 方法 | 语义 |
|---|---|---|
@app.get | GET | 获取资源 |
@app.post | POST | 创建资源 |
@app.put | PUT | 全量替换资源 |
@app.patch | PATCH | 部分更新资源 |
@app.delete | DELETE | 删除资源 |
GET 和 POST 是最常用的两个方法。PUT 和 PATCH 的区别:PUT 要求客户端提供资源的完整表示(缺失字段会被清空),PATCH 只传需要修改的字段。
二、路由分组
项目变大后,把所有路由写在 main.py 里会很乱。用 APIRouter 按模块拆分:
python
# routers/articles.py
from fastapi import APIRouter
router = APIRouter(prefix="/articles", tags=["articles"])
@router.get("/")
async def list_articles():
return [{"id": 1, "title": "入门"}]
@router.get("/{id}")
async def get_article(id: int):
return {"id": id, "title": "入门"}
@router.post("/")
async def create_article():
return {"id": 2, "title": "新文章"}python
# main.py
from fastapi import FastAPI
from routers import articles, users
app = FastAPI()
app.include_router(articles.router)
app.include_router(users.router)APIRouter 的 prefix 会给该模块下所有路由加上统一前缀。上面的 articles.py 中 @router.get("/") 实际匹配路径是 /articles/,@router.get("/{id}") 匹配 /articles/{id}。
tags 用于接口文档分组,Swagger UI 中相同 tag 的接口会显示在一起。
三、同步 vs 异步函数
FastAPI 同时支持 def 和 async def:
python
# 同步:FastAPI 在线程池中运行
@app.get("/sync")
def sync_endpoint():
return {"mode": "sync"}
# 异步:直接作为 awaitable 调度
@app.get("/async")
async def async_endpoint():
return {"mode": "async"}def | async def | |
|---|---|---|
| 适用场景 | 纯 CPU 计算、调用同步库 | 调用 async 库(如 httpx.AsyncClient、aiomysql) |
| 性能 | 线程池有上限 | 协程轻量,并发高 |
如果函数内部调用了 await,必须用 async def;如果没有 await,两种写法都可以。统一用 async def 是更常见的做法,避免混用导致的混乱。
四、路由顺序
FastAPI 按注册顺序匹配路由,第一个匹配的会被执行:
python
@app.get("/articles/fixed") # 应该放前面
async def fixed_route():
return {"type": "fixed"}
@app.get("/articles/{id}") # 放后面
async def dynamic_route(id: int):
return {"type": "dynamic", "id": id}如果反过来写,/articles/fixed 永远匹配不到——会被 /articles/{id} 截获,fixed 被当成 id 传给动态路由。
五、根路径与重定向
python
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/")
async def root():
return {"message": "运维平台 API"}
@app.get("/docs-legacy")
async def legacy_docs():
return RedirectResponse(url="/docs")六、常见错误
路由路径缺少前导斜杠
python
# 错误:FastAPI 允许,但不符合规范,可能导致 URL 拼接问题
@app.get("articles")
# 正确
@app.get("/articles")同一模块中路由前缀重复写
python
# 错误:每个路由都写完整路径
@router.get("/articles/list")
@router.get("/articles/{id}")
# 正确:用 prefix 统一加前缀
router = APIRouter(prefix="/articles")
@router.get("/list")
@router.get("/{id}")async 函数中调用同步阻塞代码
python
# 错误:在 async 函数中调用同步阻塞操作,会阻塞整个事件循环
@app.get("/slow")
async def slow():
import time
time.sleep(5) # 阻塞!其他请求都卡住
return {"done": True}
# 正确 1:用异步库
@app.get("/slow")
async def slow():
await asyncio.sleep(5) # 非阻塞,让出事件循环
return {"done": True}
# 正确 2:如果必须用同步库,改回 def 让 FastAPI 在线程池运行
@app.get("/slow")
def slow():
import time
time.sleep(5)
return {"done": True}