【FastAPI 全栈实战 | 第3篇】依赖注入与中间件 —— 写更少的代码,做更多的复用

📅 2026/7/12 18:32:39
【FastAPI 全栈实战 | 第3篇】依赖注入与中间件 —— 写更少的代码,做更多的复用
你有没有在每个路由函数里反复写page Query(1, ge1)和size Query(10, ge1, le100)有没有在每个需要认证的接口里重复 token 校验逻辑依赖注入Depends就是为解决这种重复代码而生的。它让你把公共逻辑抽取成可复用的依赖函数路由函数只关注业务本身。再加上中间件对请求/响应的统一拦截你的代码会变得前所未有的干净。目录1. 依赖注入解决了什么问题2. 泛型依赖参数提取复用3. 子依赖链Token 认证实战4. 资源生命周期依赖数据库会话5. 多依赖组合使用6. 中间件请求耗时统计7. 总结1. 依赖注入解决了什么问题看一个没有依赖注入的路由函数# 每个接口都要重复写这些参数声明router.get(/users)deflist_users(page:intQuery(1,ge1,description页码),# 重复size:intQuery(10,ge1,le100,description每页条数),# 重复authorization:strHeader(...,description认证Token),# 重复db:SessionDepends(get_db)):# 还要重复校验 token......router.get(/shops)deflist_shops(page:intQuery(1,ge1,description页码),# 又重复size:intQuery(10,ge1,le100,description每页条数),# 又重复db:SessionDepends(get_db)):...有了依赖注入后router.get(/users)deflist_users(pager:dictDepends(pagination_params),db:SessionDepends(get_db)):...router.get(/shops)deflist_shops(pager:dictDepends(pagination_params),db:SessionDepends(get_db)):...page/size 的定义写一次所有需要分页的接口直接用。这就是依赖注入的核心价值一次定义处处复用。一句话总结依赖注入的本质是参数定义的工厂模式——把公共参数抽取为独立函数路由中只需 Depends() 一行即可复用。2. 泛型依赖参数提取复用最常用的依赖注入模式把 Query/Header 参数封装成独立函数。fromfastapiimportDepends,Queryasyncdefpagination_params(page:intQuery(1,ge1,description页码),size:intQuery(10,ge1,le100,description每页条数),)-dict:把 page / size 封装成公共依赖一处定义、多处复用return{page:page,size:size,offset:(page-1)*size}为什么返回 dict 而不是 tupledict 可以按 key 取值读代码的人一眼就知道pager[offset]是什么而 tuple 的pager[2]毫无可读性。另外 dict 也方便未来扩展字段比如加个sort_by不影响已有调用方。路由中使用depends_route.get(/list,summary分页查询演示)asyncdefdemo_pagination(pager:dictDepends(pagination_params)):return{msg:分页参数,pager:pager}# pager {page: 1, size: 10, offset: 0}FastAPI 会自动识别pagination_params中的Query(...)声明在 Swagger 文档中展示 page/size 参数。一句话总结泛型依赖 把 Query / Header 参数声明移到独立函数路由中一行 Depends() 即可复用。3. 子依赖链Token 认证实战依赖函数可以调用另一个依赖函数形成依赖链。这是实现认证中间层的推荐方式。# 第一层Token 基础校验叶子依赖asyncdefverify_token(authorization:strHeader(...,description认证 Token)):最底层从 Header 拿 token 并校验ifnotauthorization.startswith(Bearer ):raiseHTTPException(status_code401,detailToken 格式错误应为 Bearer xxx)tokenauthorization[len(Bearer ):]iftoken!123456:# 模拟校验生产环境应查数据库或调用OAuthraiseHTTPException(status_code401,detailToken 无效或已过期)returntoken# 第二层根据 token 获取当前用户中间依赖asyncdefget_current_user(token:strDepends(verify_token)):中间层拿到已验证的 token查询用户信息# 模拟数据库查询return{user_id:1,username:admin,role:superuser}依赖链的执行流程请求 → get_current_user() → Depends(verify_token) → 校验Header ↓ 校验通过 token 传给 get_current_user ↓ 返回 user 字典为什么要拆成两层verify_token是通用的 token 校验逻辑任何需要认证的地方都能复用。get_current_user是根据 token 查用户的业务逻辑。拆开后如果你有另一个接口只需要校验 token 但不关心用户信息直接用verify_token即可。一句话总结子依赖链 把复杂逻辑拆成多层每层只做一件事。依赖失败直接抛异常FastAPI 自动终止链路后面的代码不会执行。4. 资源生命周期依赖数据库会话这是 FastAPI 中最经典的依赖注入模式——用生成器函数管理需要打开→使用→关闭的资源。fromcore.config.pg_configimportSessionLocaldefget_db():数据库会话依赖路由执行完自动关闭会话dbSessionLocal()try:yielddbfinally:db.close()# 无论路由是否抛异常都会执行为什么用yield而不是returnyield让 FastAPI 知道这个依赖有清理阶段。执行流程请求进来 → FastAPI 执行db SessionLocal()创建会话yield db→ 把会话注入路由函数路由函数执行业务逻辑可能有异常finally中的db.close()无论如何都会执行对比手写 try/finally如果不用依赖注入你需要在每个路由函数里写dbSessionLocal()try:...finally:db.close()每个函数 6 行模板代码10 个路由就是 60 行而且容易忘写db.close()导致连接泄漏。一句话总结生成器依赖 FastAPI 的with语句自动管理资源的创建和释放告别连接泄漏。5. 多依赖组合使用一个路由可以同时注入多个依赖各管各的depends_route.get(/secure-list,summary分页 认证组合)asyncdefdemo_combined(pager:dictDepends(pagination_params),current_user:dictDepends(get_current_user),):两个依赖同时注入受保护的分页查询return{msg:受保护的分页查询,user:current_user[username],pager:pager,data:[{id:1,name:item-1},{id:2,name:item-2}],}依赖的组合方式模式写法场景单个依赖Depends(get_db)数据库会话多个并排Depends(A), Depends(B)分页 认证依赖嵌套Depends(B)内部调Depends(A)认证链混合使用Depends(A), Depends(B(Depends(C)))复杂场景一句话总结依赖之间互不干扰可以自由组合FastAPI 自动处理依赖的解析顺序和参数传递。6. 中间件请求耗时统计依赖注入作用于特定路由而中间件作用于所有请求。适合放日志、CORS、请求耗时统计等全局逻辑。importtimefromstarlette.middleware.baseimportBaseHTTPMiddlewarefromfastapiimportRequestclassTimingMiddleware(BaseHTTPMiddleware):记录每个请求的处理耗时注入 X-Process-Time 响应头asyncdefdispatch(self,request:Request,call_next):starttime.perf_counter()responseawaitcall_next(request)# 执行后续中间件 → 路由处理elapsedtime.perf_counter()-start response.headers[X-Process-Time]f{elapsed:.4f}sreturnresponse注册中间件# 内置中间件CORS 跨域app.add_middleware(CORSMiddleware,allow_origins[*],# 生产环境应限制具体域名allow_credentialsTrue,allow_methods[*],allow_headers[*],)# 自定义中间件耗时统计app.add_middleware(TimingMiddleware)中间件的执行顺序是后进先出LIFO请求按注册顺序从上到下执行call_next之前的代码响应按反方向执行call_next之后的代码。请求进入 → CORSMiddleware.dispatch (之前) → TimingMiddleware.dispatch (start计时) → call_next → 路由处理 → TimingMiddleware.dispatch (stop计时, 写Header) → CORSMiddleware.dispatch (之后) → 响应返回一句话总结依赖注入解决特定路由的代码复用中间件解决所有路由的统一拦截两者互补覆盖了请求处理的前后全流程。7. 总结FastAPI 的依赖注入体系用三种模式覆盖了主要的代码复用场景模式工具场景泛型依赖Depends(params_fn)分页参数、筛选条件等公共 Query/Header子依赖链Depends(fn(Depends(...)))认证校验token→用户→权限资源生命周期生成器 yield数据库会话、文件句柄等需要关闭的资源中间件则是全局维度的拦截器适合日志、监控、CORS 等横切关注点。如果用一句话总结FastAPI 的 Depends 让你把参数定义和业务逻辑分离中间件让你把横切逻辑和路由处理分离——两者的共同目标是让你的路由函数只写业务代码。