你是不是也遇到过这种场景:在中间件里好不容易解析出了用户ID,到了路由处理函数里,却还得再查一遍数据库?或者依赖项里计算好的权限信息,想传递给后续业务逻辑,只能吭哧吭哧用全局变量?
🎯 今天咱们就聊聊FastAPI里那个低调却强大的Request.state 存储机制。我用一个真实的"用户画像"案例,带你走一遍从踩坑到优雅解决的全过程。保证你看完,就能让中间件、依赖和路由处理程序之间"手拉手"愉快地共享数据!
📌 本文将带你搞懂:
-
为什么全局变量是"毒药"?
-
Request.state 的正确打开方式
-
一个完整的实战:在中间件中注入用户信息,通过依赖传递,最后在路由中使用。
-
避坑指南:State的生命周期、并发安全性,以及和Contextvar的恩怨情仇。
🍜 故事从一碗牛肉面说起
想象一下,你开了一家牛肉面馆。每个客人进来,你都得先问"要辣吗?""要香菜吗?"。这个收集信息的过程,就像是 中间件 。然后后厨根据这些信息,决定放多少辣、加不加香菜,这就像 依赖项 。最后,一碗定制化的面端到客人面前,这就是 路由处理程序。
如果你把"加辣"、"加香菜"这些信息写在收银台的一张公共小黑板上(全局变量),那当两个客人同时点餐时,后厨肯定会看花眼,把A客人的辣面端给B客人。这就是并发下的数据混乱!
FastAPI 的 Request.state 就相当于给每个客人发了一张专属的"点餐小票",从收银台到后厨,这张小票全程跟着这碗面,绝对不会搞混。
🚀 实战:给每个请求贴上"用户画像"
好,不废话了,咱们直接上代码。假设我们有一个场景:需要从请求头中获取用户令牌,解析出用户信息,然后在后续的所有逻辑中都能用到这个用户信息。
1️⃣ 最傻最暴力的错误示范
# 全局变量,并发地狱的入口
current_user = None
@app.middleware("http")
async def add_user_middleware(request, call_next):
global current_user
token = request.headers.get("Authorization")
# 模拟解析用户
current_user = {"id": 1, "name": "张三"}
response = await call_next(request)
return response
@app.get("/profile")
async def get_profile():
# 直接读取全局变量,多个请求会相互覆盖!!!
return current_user
这里千万别学这种偷懒写的代码,否则半夜收到报警短信,说用户A看到了用户B的隐私信息,那就只能等着哭了。😭 全局变量在并发请求下就是个定时炸弹。
2️⃣ 优雅的救世主:Request.state
接下来重点来了!FastAPI 的 request.state 才是我们想要的"点餐小票"。它依附于每一个独立的请求对象,天然就是线程/协程安全的。
from fastapi import FastAPI, Request, Depends
from pydantic import BaseModel
app = FastAPI()
# 1. 中间件:负责把用户信息"贴"到 state 上
@app.middleware("http")
async def add_user_middleware(request: Request, call_next):
# 假设从请求头获取token
token = request.headers.get("Authorization")
user_info = {"id": 1, "name": "李四", "role": "admin"}
# 关键操作:把数据挂在 request.state 上
request.state.user = user_info
response = await call_next(request)
return response
# 2. 依赖项:负责从 state 中取出数据,并注入给路由
async def get_current_user(request: Request):
# 从 state 中获取之前挂载的数据
user = request.state.user
# 可以在这里做一些校验、权限判断
if not user:
# 也可以抛出异常
return None
return user
# 3. 路由处理程序:通过依赖项获取数据,清爽无比
@app.get("/profile")
async def get_profile(user: dict = Depends(get_current_user)):
# 直接使用,再也不用担心数据乱串
return {"msg": f"Hello, {user['name']}", "user": user}
看到没?整个流程清晰得像流水线:中间件负责写request.state.user,依赖项负责读并注入,路由函数只管用。每个请求的state都是独立的,再也不用担心并发问题。
💡 再说个容易翻车的点
你可能会问:"那我把所有数据都塞进 request.state 里不就行了?" 别,这里有个性能陷阱!
request.state 本质上是一个简单的对象,它适合存储元数据 ,比如用户ID、权限标识、请求开始时间等。如果你往里塞一个几MB的图片数据,或者一个包含上百个字段的复杂对象,那内存占用和GC压力会直线上升。我个人习惯是:state里只存**"钥匙"**,比如用户ID,真正的用户大数据,比如详细资料、权限树,还是在依赖项里用ID去缓存里拿,这样更稳。
🧠 进阶思考:State和Contextvar是啥关系?
如果你接触过Python的异步编程,可能会想到contextvars 。它也能在异步上下文中传递数据。那什么时候用request.state ,什么时候用 contextvars?
我的理解是:request.state 是FastAPI框架层提供的,和Request生命周期绑定,在中间件、依赖、路由之间传递数据是它的本职工作,用起来最直接。而contextvars 更底层,适合在跨函数、跨模块的复杂异步调用链中传递上下文(比如分布式追踪的trace_id)。但要注意,千万不要混用导致数据混乱 。我自己写业务代码时,99%的情况用 request.state 就够了,简单又可靠。
📝 总结
好啦,今天这顿饭咱们就吃到这儿。简单回顾一下:
✅ 告别全局变量,拥抱 request.state 做请求级别的数据存储。
✅ 中间件负责写,依赖项负责读和注入,路由负责使用,各司其职。
✅ 警惕state里存大对象,只存"钥匙"才是优雅之道。
✅ 理解 request.state 和 contextvars 的区别,选对工具。
最后啰嗦一句,技术这东西,光看是记不住的,一定要自己敲一遍。赶紧打开你的编辑器,把上面那个用户画像的例子跑起来,然后换成你自己的业务逻辑试试。
如果你觉得这篇文章对你有帮助,或者你也踩过类似的坑,欢迎点个❤️收藏,或者分享给正在被FastAPI折磨的小伙伴。咱们下期再见,继续聊技术,聊生活~