FastAPI 入门阶段,大家写的通常都是这种代码:
python
@app.get("/hello")
async def hello():
return {"msg": "hello"}
一个接口,一个函数,简单直接。
但项目稍微往前走一步,问题马上来了。
每个请求都要记录耗时,写在哪里?
每个接口都要拿数据库会话,写在哪里?
新闻列表和用户列表都要分页参数,难道每个函数都复制一遍 page 和 pageSize?
登录后的接口都要获取当前用户,难道每个接口都手动解析 Authorization?
这些问题靠堆函数参数和复制代码也能解决,但项目很快会变乱。
FastAPI 给了两个非常关键的工程化工具:
- Middleware,中间件
- Depends,依赖注入
一句话区分:
中间件处理所有请求共同经过的外层逻辑,依赖注入处理某些接口需要的可复用资源或参数。
本篇准备
这一篇只需要 FastAPI 基础环境:
bash
pip install fastapi uvicorn
文中会用数据库会话举例,但这里只讲依赖注入的资源生命周期。SQLAlchemy 的安装、建库和 CRUD 会放到下一篇展开。
1. Middleware,中间件像请求外面的一层包装
先看最小写法:
python
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def log_middleware(request: Request, call_next):
print("请求进入")
response = await call_next(request)
print("响应返回")
return response
这里最重要的是 call_next(request)。
它表示把请求继续交给下游,也就是下一个中间件或者最终的路由函数。
如果你不调用它,请求就不会继续往后走。
中间件的执行流程像这样:
Route Middleware Client Route Middleware Client #mermaid-svg-49idUlZDvUQ4sxrV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-49idUlZDvUQ4sxrV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-49idUlZDvUQ4sxrV .error-icon{fill:#552222;}#mermaid-svg-49idUlZDvUQ4sxrV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-49idUlZDvUQ4sxrV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-49idUlZDvUQ4sxrV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-49idUlZDvUQ4sxrV .marker.cross{stroke:#333333;}#mermaid-svg-49idUlZDvUQ4sxrV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-49idUlZDvUQ4sxrV p{margin:0;}#mermaid-svg-49idUlZDvUQ4sxrV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-49idUlZDvUQ4sxrV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-49idUlZDvUQ4sxrV .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-49idUlZDvUQ4sxrV .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-49idUlZDvUQ4sxrV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-49idUlZDvUQ4sxrV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-49idUlZDvUQ4sxrV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-49idUlZDvUQ4sxrV .sequenceNumber{fill:white;}#mermaid-svg-49idUlZDvUQ4sxrV #sequencenumber{fill:#333;}#mermaid-svg-49idUlZDvUQ4sxrV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-49idUlZDvUQ4sxrV .messageText{fill:#333;stroke:none;}#mermaid-svg-49idUlZDvUQ4sxrV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-49idUlZDvUQ4sxrV .labelText,#mermaid-svg-49idUlZDvUQ4sxrV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-49idUlZDvUQ4sxrV .loopText,#mermaid-svg-49idUlZDvUQ4sxrV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-49idUlZDvUQ4sxrV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-49idUlZDvUQ4sxrV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-49idUlZDvUQ4sxrV .noteText,#mermaid-svg-49idUlZDvUQ4sxrV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-49idUlZDvUQ4sxrV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-49idUlZDvUQ4sxrV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-49idUlZDvUQ4sxrV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-49idUlZDvUQ4sxrV .actorPopupMenu{position:absolute;}#mermaid-svg-49idUlZDvUQ4sxrV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-49idUlZDvUQ4sxrV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-49idUlZDvUQ4sxrV .actor-man circle,#mermaid-svg-49idUlZDvUQ4sxrV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-49idUlZDvUQ4sxrV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求进入 请求前逻辑 await call_next(request) response 响应后逻辑 返回 response
这就是中间件最适合做的事情:
- 记录请求日志
- 统计接口耗时
- 设置统一响应头
- CORS 跨域
- 全局链路追踪
- 某些统一认证或限流
它不适合做具体业务。
比如"新增一本书""收藏一条新闻"这种业务逻辑,不应该塞进中间件。
2. 多个中间件的执行顺序
如果有多个中间件,执行过程不是简单从上到下跑完。
它更像洋葱。
外层先进入,内层后进入;响应返回时,内层先出来,外层后出来。
Route middleware2 middleware1 Client Route middleware2 middleware1 Client #mermaid-svg-QnBgHjcoEYZs0xTP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QnBgHjcoEYZs0xTP .error-icon{fill:#552222;}#mermaid-svg-QnBgHjcoEYZs0xTP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QnBgHjcoEYZs0xTP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QnBgHjcoEYZs0xTP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QnBgHjcoEYZs0xTP .marker.cross{stroke:#333333;}#mermaid-svg-QnBgHjcoEYZs0xTP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QnBgHjcoEYZs0xTP p{margin:0;}#mermaid-svg-QnBgHjcoEYZs0xTP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QnBgHjcoEYZs0xTP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QnBgHjcoEYZs0xTP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QnBgHjcoEYZs0xTP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QnBgHjcoEYZs0xTP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QnBgHjcoEYZs0xTP .sequenceNumber{fill:white;}#mermaid-svg-QnBgHjcoEYZs0xTP #sequencenumber{fill:#333;}#mermaid-svg-QnBgHjcoEYZs0xTP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QnBgHjcoEYZs0xTP .messageText{fill:#333;stroke:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QnBgHjcoEYZs0xTP .labelText,#mermaid-svg-QnBgHjcoEYZs0xTP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .loopText,#mermaid-svg-QnBgHjcoEYZs0xTP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QnBgHjcoEYZs0xTP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-QnBgHjcoEYZs0xTP .noteText,#mermaid-svg-QnBgHjcoEYZs0xTP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-QnBgHjcoEYZs0xTP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QnBgHjcoEYZs0xTP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QnBgHjcoEYZs0xTP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QnBgHjcoEYZs0xTP .actorPopupMenu{position:absolute;}#mermaid-svg-QnBgHjcoEYZs0xTP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QnBgHjcoEYZs0xTP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QnBgHjcoEYZs0xTP .actor-man circle,#mermaid-svg-QnBgHjcoEYZs0xTP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-QnBgHjcoEYZs0xTP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} request call_next call_next response response response
这也是为什么统计耗时很适合放在中间件里。
你可以在 call_next 前记录开始时间,在 call_next 后计算总耗时。
python
import time
from fastapi import Request
@app.middleware("http")
async def timing_middleware(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
response.headers["X-Process-Time"] = str(duration)
return response
这个逻辑和具体接口无关,所有请求都应该经过,所以放中间件刚好。
3. Depends,依赖注入用来复用参数和资源
再看另一个问题。
新闻列表需要分页:
python
@app.get("/news/list")
async def get_news_list(skip: int = 0, limit: int = 10):
...
用户列表也需要分页:
python
@app.get("/user/list")
async def get_user_list(skip: int = 0, limit: int = 10):
...
如果每个列表接口都这么写,校验规则很快会复制得到处都是。
这时可以抽一个依赖函数:
python
from fastapi import Depends, Query
async def common_pagination(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100)
):
return {"skip": skip, "limit": limit}
@app.get("/news/list")
async def get_news_list(pagination=Depends(common_pagination)):
return pagination
@app.get("/user/list")
async def get_user_list(pagination=Depends(common_pagination)):
return pagination
FastAPI 会先调用 common_pagination,再把返回值注入给路由函数。
#mermaid-svg-KFjY62LPmNYx9Cjr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KFjY62LPmNYx9Cjr .error-icon{fill:#552222;}#mermaid-svg-KFjY62LPmNYx9Cjr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KFjY62LPmNYx9Cjr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KFjY62LPmNYx9Cjr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KFjY62LPmNYx9Cjr .marker.cross{stroke:#333333;}#mermaid-svg-KFjY62LPmNYx9Cjr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KFjY62LPmNYx9Cjr p{margin:0;}#mermaid-svg-KFjY62LPmNYx9Cjr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster-label text{fill:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster-label span{color:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster-label span p{background-color:transparent;}#mermaid-svg-KFjY62LPmNYx9Cjr .label text,#mermaid-svg-KFjY62LPmNYx9Cjr span{fill:#333;color:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr .node rect,#mermaid-svg-KFjY62LPmNYx9Cjr .node circle,#mermaid-svg-KFjY62LPmNYx9Cjr .node ellipse,#mermaid-svg-KFjY62LPmNYx9Cjr .node polygon,#mermaid-svg-KFjY62LPmNYx9Cjr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KFjY62LPmNYx9Cjr .rough-node .label text,#mermaid-svg-KFjY62LPmNYx9Cjr .node .label text,#mermaid-svg-KFjY62LPmNYx9Cjr .image-shape .label,#mermaid-svg-KFjY62LPmNYx9Cjr .icon-shape .label{text-anchor:middle;}#mermaid-svg-KFjY62LPmNYx9Cjr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KFjY62LPmNYx9Cjr .rough-node .label,#mermaid-svg-KFjY62LPmNYx9Cjr .node .label,#mermaid-svg-KFjY62LPmNYx9Cjr .image-shape .label,#mermaid-svg-KFjY62LPmNYx9Cjr .icon-shape .label{text-align:center;}#mermaid-svg-KFjY62LPmNYx9Cjr .node.clickable{cursor:pointer;}#mermaid-svg-KFjY62LPmNYx9Cjr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KFjY62LPmNYx9Cjr .arrowheadPath{fill:#333333;}#mermaid-svg-KFjY62LPmNYx9Cjr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KFjY62LPmNYx9Cjr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KFjY62LPmNYx9Cjr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KFjY62LPmNYx9Cjr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KFjY62LPmNYx9Cjr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KFjY62LPmNYx9Cjr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster text{fill:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr .cluster span{color:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KFjY62LPmNYx9Cjr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KFjY62LPmNYx9Cjr rect.text{fill:none;stroke-width:0;}#mermaid-svg-KFjY62LPmNYx9Cjr .icon-shape,#mermaid-svg-KFjY62LPmNYx9Cjr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KFjY62LPmNYx9Cjr .icon-shape p,#mermaid-svg-KFjY62LPmNYx9Cjr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KFjY62LPmNYx9Cjr .icon-shape .label rect,#mermaid-svg-KFjY62LPmNYx9Cjr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KFjY62LPmNYx9Cjr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KFjY62LPmNYx9Cjr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KFjY62LPmNYx9Cjr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET /news/list?skip=0&limit=10
FastAPI 解析请求
调用 common_pagination
校验 skip / limit
返回 pagination
注入路由函数
返回响应
这就是依赖注入最直观的用途。
它把重复的参数解析、校验、资源准备抽出来,让路由函数只关心业务。
4. yield 依赖,适合管理资源生命周期
普通依赖直接 return。
但数据库会话这种资源,不只是创建,还要关闭。
这时就适合用 yield。
课程里的数据库依赖大概是这个思路:
python
from sqlalchemy.ext.asyncio import AsyncSession
async def get_database():
async with AsyncSessionLocal() as session:
try:
yield session
except Exception:
await session.rollback()
raise
可以这么理解:
text
yield 前,准备资源
yield 的值,交给路由函数
yield 后,做清理收尾
画成流程:
AsyncSession Route get_database FastAPI AsyncSession Route get_database FastAPI #mermaid-svg-RVu4Enefbaein5jb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RVu4Enefbaein5jb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RVu4Enefbaein5jb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RVu4Enefbaein5jb .error-icon{fill:#552222;}#mermaid-svg-RVu4Enefbaein5jb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RVu4Enefbaein5jb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RVu4Enefbaein5jb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RVu4Enefbaein5jb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RVu4Enefbaein5jb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RVu4Enefbaein5jb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RVu4Enefbaein5jb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RVu4Enefbaein5jb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RVu4Enefbaein5jb .marker.cross{stroke:#333333;}#mermaid-svg-RVu4Enefbaein5jb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RVu4Enefbaein5jb p{margin:0;}#mermaid-svg-RVu4Enefbaein5jb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RVu4Enefbaein5jb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-RVu4Enefbaein5jb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RVu4Enefbaein5jb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-RVu4Enefbaein5jb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-RVu4Enefbaein5jb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-RVu4Enefbaein5jb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-RVu4Enefbaein5jb .sequenceNumber{fill:white;}#mermaid-svg-RVu4Enefbaein5jb #sequencenumber{fill:#333;}#mermaid-svg-RVu4Enefbaein5jb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-RVu4Enefbaein5jb .messageText{fill:#333;stroke:none;}#mermaid-svg-RVu4Enefbaein5jb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RVu4Enefbaein5jb .labelText,#mermaid-svg-RVu4Enefbaein5jb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-RVu4Enefbaein5jb .loopText,#mermaid-svg-RVu4Enefbaein5jb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-RVu4Enefbaein5jb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RVu4Enefbaein5jb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-RVu4Enefbaein5jb .noteText,#mermaid-svg-RVu4Enefbaein5jb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-RVu4Enefbaein5jb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RVu4Enefbaein5jb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RVu4Enefbaein5jb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RVu4Enefbaein5jb .actorPopupMenu{position:absolute;}#mermaid-svg-RVu4Enefbaein5jb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-RVu4Enefbaein5jb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RVu4Enefbaein5jb .actor-man circle,#mermaid-svg-RVu4Enefbaein5jb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-RVu4Enefbaein5jb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 路由异常 调用依赖 创建 session yield session 执行业务逻辑 rollback async with 退出并关闭会话
这里有个实际项目里很重要的点。
事务边界要统一。
本文采用一个更容易维护的约定:
依赖负责创建、注入、异常回滚和关闭会话。
写操作负责自己显式 commit()。
这样哪个接口修改了数据库,一眼能看到。
如果团队选择"依赖统一提交",也可以,但就不要在路由或服务层再手动 commit()。
关键是别两边都做一半。
5. Depends 还能做认证
依赖注入还有一个非常典型的用途,获取当前登录用户。
项目里的受保护接口大概会这样写:
python
@router.get("/info")
async def get_user_info(current_user: User = Depends(get_current_user)):
user_info = UserInfoResponse.model_validate(current_user)
return success_response(data=user_info)
这里的 UserInfoResponse 是用户信息响应模型,后面用户模块会专门展开。先记住一点就够了:不要把 ORM 用户对象原样返回给前端。
get_current_user 会做这些事:
- 从请求头读取
Authorization - 查
user_token表 - 判断 Token 是否存在、是否过期
- 查出对应用户
- 把用户对象注入到接口函数里
于是业务接口不需要关心 Token 怎么解析,只需要拿到 current_user。
#mermaid-svg-FKgcQ9JEZ0QMlcyu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .error-icon{fill:#552222;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .marker.cross{stroke:#333333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu p{margin:0;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster-label text{fill:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster-label span{color:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster-label span p{background-color:transparent;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .label text,#mermaid-svg-FKgcQ9JEZ0QMlcyu span{fill:#333;color:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .node rect,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node circle,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node ellipse,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node polygon,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .rough-node .label text,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node .label text,#mermaid-svg-FKgcQ9JEZ0QMlcyu .image-shape .label,#mermaid-svg-FKgcQ9JEZ0QMlcyu .icon-shape .label{text-anchor:middle;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .rough-node .label,#mermaid-svg-FKgcQ9JEZ0QMlcyu .node .label,#mermaid-svg-FKgcQ9JEZ0QMlcyu .image-shape .label,#mermaid-svg-FKgcQ9JEZ0QMlcyu .icon-shape .label{text-align:center;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .node.clickable{cursor:pointer;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .arrowheadPath{fill:#333333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FKgcQ9JEZ0QMlcyu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FKgcQ9JEZ0QMlcyu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster text{fill:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .cluster span{color:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FKgcQ9JEZ0QMlcyu rect.text{fill:none;stroke-width:0;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .icon-shape,#mermaid-svg-FKgcQ9JEZ0QMlcyu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .icon-shape p,#mermaid-svg-FKgcQ9JEZ0QMlcyu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .icon-shape .label rect,#mermaid-svg-FKgcQ9JEZ0QMlcyu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FKgcQ9JEZ0QMlcyu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FKgcQ9JEZ0QMlcyu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FKgcQ9JEZ0QMlcyu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
受保护接口
Depends(get_current_user)
读取 Authorization
查询 Token
有效吗
抛出 401
注入 current_user
这就是依赖注入比中间件更适合认证结果注入的原因。
中间件可以做统一拦截,但依赖可以把"当前用户"直接作为函数参数交给业务代码。
6. Middleware 和 Depends 怎么选
这张表很实用:
| 需求 | 更适合 | 原因 |
|---|---|---|
| 每个请求记录耗时 | Middleware | 所有请求都要经过 |
| 全局 CORS | Middleware | 属于 HTTP 外层规则 |
| 所有响应加请求 ID | Middleware | 统一处理响应头 |
| 分页参数复用 | Depends | 只有部分接口需要 |
| 数据库 Session | Depends | 路由函数需要拿到资源 |
| 当前登录用户 | Depends | 业务函数需要拿到用户对象 |
| 权限校验 | Depends | 可以按接口精细声明 |
一句话:
中间件是全局外壳,依赖注入是按需装配。
7. 生命周期,startup 和 lifespan
课程里常见写法是:
python
@app.on_event("startup")
async def on_startup():
await create_tables()
这种写法在很多旧教程里都能看到,学习时没问题。
但 FastAPI 官方现在更推荐使用 lifespan 来统一管理启动和关闭逻辑。
示例:
python
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
await create_tables()
yield
# 应用关闭时可以在这里释放资源
app = FastAPI(lifespan=lifespan)
两者核心目的相同,都是管理应用生命周期。
区别是 lifespan 把启动和关闭放在一个上下文里,更适合管理成对资源,比如连接池、模型加载、后台任务等。
另外,yield 依赖的清理时机在 FastAPI 不同版本里有过调整。写流式响应、数据库会话这类代码时,最好看当前项目使用版本对应的官方文档,不要只按旧教程记忆。
8. 在 AI 掘金头条项目里的位置
项目里这几个地方最能体现中间件和依赖注入:
| 位置 | 用法 |
|---|---|
main.py |
注册 CORS 中间件,挂载业务路由 |
config/db_conf.py |
get_db 依赖创建数据库会话 |
utils/auth.py |
get_current_user 依赖解析 Token |
routers/*.py |
路由函数通过 Depends 拿数据库和当前用户 |
请求进入项目后,大概是这条链路:
MySQL Router Depends CORS/Middleware Client MySQL Router Depends CORS/Middleware Client #mermaid-svg-XNM9ujNmUnGTQd26{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XNM9ujNmUnGTQd26 .error-icon{fill:#552222;}#mermaid-svg-XNM9ujNmUnGTQd26 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XNM9ujNmUnGTQd26 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XNM9ujNmUnGTQd26 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XNM9ujNmUnGTQd26 .marker.cross{stroke:#333333;}#mermaid-svg-XNM9ujNmUnGTQd26 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XNM9ujNmUnGTQd26 p{margin:0;}#mermaid-svg-XNM9ujNmUnGTQd26 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XNM9ujNmUnGTQd26 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XNM9ujNmUnGTQd26 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XNM9ujNmUnGTQd26 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XNM9ujNmUnGTQd26 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XNM9ujNmUnGTQd26 .sequenceNumber{fill:white;}#mermaid-svg-XNM9ujNmUnGTQd26 #sequencenumber{fill:#333;}#mermaid-svg-XNM9ujNmUnGTQd26 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XNM9ujNmUnGTQd26 .messageText{fill:#333;stroke:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XNM9ujNmUnGTQd26 .labelText,#mermaid-svg-XNM9ujNmUnGTQd26 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .loopText,#mermaid-svg-XNM9ujNmUnGTQd26 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XNM9ujNmUnGTQd26 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XNM9ujNmUnGTQd26 .noteText,#mermaid-svg-XNM9ujNmUnGTQd26 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XNM9ujNmUnGTQd26 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XNM9ujNmUnGTQd26 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XNM9ujNmUnGTQd26 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XNM9ujNmUnGTQd26 .actorPopupMenu{position:absolute;}#mermaid-svg-XNM9ujNmUnGTQd26 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XNM9ujNmUnGTQd26 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XNM9ujNmUnGTQd26 .actor-man circle,#mermaid-svg-XNM9ujNmUnGTQd26 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XNM9ujNmUnGTQd26 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 请求 准备依赖 创建会话或验证 Token 注入 db/current_user 执行业务查询 返回数据 返回响应 响应返回
9. 小结
中间件和依赖注入,是 FastAPI 从玩具接口走向真实项目的分界线。
中间件负责所有请求共同经过的外层规则。
依赖注入负责把可复用的参数、资源、认证结果按需交给路由函数。
如果你把日志、耗时、CORS 放在中间件里,把数据库会话、分页参数、当前用户放在依赖里,项目结构通常就不会太离谱。
下一篇,我们继续往下走,看看数据库这条线。
FastAPI + SQLAlchemy Async,才是真正进入后端项目实战。