FastAPI筑基_Day12_Depends依赖注入精讲

0 阅读6分钟

【FastAPI筑基-Day12】Depends依赖注入精讲|公共参数抽取、接口登录鉴权、权限拦截(企业必备)

专栏:FastAPI零基础后端实战系列 标签:FastAPI、Depends、依赖注入、接口鉴权、Token校验、权限控制 前置学习:Day11 模块化路由分层工程结构


一、前言

这篇讲 FastAPI 最核心的设计之一 —— Depends 依赖注入。我们会从最简单的函数依赖开始,逐步实现公共参数抽取、Token 登录鉴权、角色权限拦截,最终产出一套可直接运行的完整鉴权方案。学完本篇,你将掌握企业后端开发中最高频的代码复用与接口安全能力。

在真实后端开发中,90% 接口都存在重复逻辑:

  • 获取当前登录用户
  • 校验 Token 是否有效
  • 统一获取分页参数 page、size
  • 接口权限判断

如果每个接口都写一遍,代码极度冗余、维护灾难!

FastAPI 最强神器之一 —— Depends 依赖注入

一句话总结:把公共逻辑抽离出来,所有接口直接复用,无需重复编码。

Day12 带你从零吃透 Depends,实现:

  • ✅ 公共参数依赖抽取(分页参数通用封装)
  • ✅ 基础函数形式依赖注入
  • ✅ 类形式依赖注入(高级用法)
  • ✅ 全局接口 Token 登录鉴权
  • ✅ 局部接口权限拦截

二、什么是 Depends 依赖注入?

依赖注入(Dependency Injection)是 FastAPI 核心设计思想。

作用:将通用、重复、公共逻辑抽离为依赖函数,接口直接声明依赖即可自动执行、自动传参。

打个比方:你去餐厅点菜,不需要自己进厨房洗菜、切菜、炒菜,你只需要告诉服务员"来一份番茄炒蛋",厨房(依赖函数)自动帮你搞定。Depends 就是那个服务员 —— 你声明需要什么,它自动帮你准备好再递给你。

graph TD
    A[接口收到请求] --> B[FastAPI 解析 Depends]
    B --> C[自动调用依赖函数]
    C --> D{依赖校验通过?}
    D -->|是| E[将结果注入接口参数]
    D -->|否| F[直接返回错误,接口不执行]
    E --> G[接口正常执行业务逻辑]

核心流程:请求进来 → 触发依赖 → 校验/处理 → 注入结果 → 接口执行。


三、函数形式依赖注入(分页参数抽取)

最常见的场景:多个列表接口都需要分页参数 pagesize

不用 Depends 的写法(每个接口重复声明):

@router.get("/users")
def get_users(page: int = 1, size: int = 10):
    ...

@router.get("/orders")
def get_orders(page: int = 1, size: int = 10):
    ...

用 Depends 的写法(抽一次,处处复用):

from fastapi import APIRouter, Depends

router = APIRouter()

def page_params(page: int = 1, size: int = 10):
    """公共分页参数依赖,统一限制最大 size"""
    return {"page": page, "size": min(size, 100)}

@router.get("/users")
def get_users(pager: dict = Depends(page_params)):
    return {"code": 200, "pager": pager}

@router.get("/orders")
def get_orders(pager: dict = Depends(page_params)):
    return {"code": 200, "pager": pager}

效果:接口无需声明 page、size,自动获取、自动校验、统一限制最大 size!修改分页逻辑只需改 page_params 一处。


四、类形式依赖注入(高级企业写法)

当逻辑复杂、需要多个方法复用,使用类依赖更优雅。

from fastapi import Depends

class PageDepend:
    def __init__(self, page: int = 1, size: int = 10):
        self.page = page
        self.size = min(size, 100)

    def offset(self):
        return (self.page - 1) * self.size

@router.get("/page/data")
def page_data(pd: PageDepend = Depends()):
    return {
        "page": pd.page,
        "size": pd.size,
        "offset": pd.offset()
    }

类依赖的优势:

  • __init__ 接收参数,FastAPI 自动解析
  • 可以封装多个工具方法(如 offset()total_pages() 等)
  • 适合复杂公共逻辑:分页、排序、筛选组合

五、核心实战:基于 Depends 实现 Token 登录鉴权

业务场景:部分接口需要登录后才能访问。

实现逻辑

  1. 请求头携带 Token
  2. 依赖自动拦截校验 Token
  3. 无效 Token 直接 401 拦截
  4. 校验通过自动解析用户信息注入接口

1. 编写登录鉴权依赖

from fastapi import Header, HTTPException

VALID_TOKEN = "admin123456"

def get_current_user(token: str = Header(None, description="登录令牌")):
    if not token:
        raise HTTPException(status_code=401, detail="未登录,请先授权")
    if token != VALID_TOKEN:
        raise HTTPException(status_code=401, detail="Token无效,登录失败")

    return {
        "user_id": 1001,
        "username": "管理员",
        "role": "admin"
    }

2. 接口引入登录依赖

@router.get("/user/profile")
def user_profile(user: dict = Depends(get_current_user)):
    """个人信息接口,必须登录"""
    return {
        "msg": "查询成功",
        "user_info": user
    }

效果:不带 Token / Token 错误直接拦截,无法进入接口逻辑!


六、全局依赖(所有接口统一拦截)

如果整个模块全部需要登录,无需每个接口写 Depends:

from fastapi import APIRouter, Depends

router = APIRouter(
    prefix="/admin",
    tags=["后台管理"],
    dependencies=[Depends(get_current_user)]
)

@router.get("/index")
def admin_index():
    return {"msg": "后台管理首页,已自动鉴权"}

超级适合:后台管理模块、需要统一登录的业务模块。该路由下所有接口自动校验 Token,无需逐个添加。


七、拓展:基于依赖实现角色权限控制

在登录依赖基础上,二次封装权限拦截(依赖嵌套):

from fastapi import Depends, HTTPException

def admin_required(user: dict = Depends(get_current_user)):
    if user.get("role") != "admin":
        raise HTTPException(status_code=403, detail="权限不足,仅管理员可访问")
    return user

@router.get("/admin/only", dependencies=[Depends(admin_required)])
def admin_only_api():
    return {"msg": "管理员专属接口访问成功"}
graph TD
    A[请求进入] --> B[get_current_user 校验 Token]
    B -->|Token 无效| C[401 未登录]
    B -->|Token 有效| D[admin_required 校验角色]
    D -->|非 admin| E[403 权限不足]
    D -->|是 admin| F[接口正常执行]

完美实现:登录校验 + 角色权限双层拦截。依赖可以嵌套组合,像搭积木一样灵活。


八、Day12 完整整合代码(可直接运行)

from fastapi import FastAPI, Depends, Header, HTTPException
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(title="Day12 Depends依赖注入实战")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---------------------- 1. 分页公共依赖 ----------------------
def page_params(page: int = 1, size: int = 10):
    return {"page": page, "size": min(size, 100)}

# ---------------------- 2. 登录鉴权依赖 ----------------------
VALID_TOKEN = "admin123456"

def get_current_user(token: str = Header(None)):
    if not token:
        raise HTTPException(401, "未登录")
    if token != VALID_TOKEN:
        raise HTTPException(401, "Token无效")
    return {"user_id": 1001, "username": "管理员", "role": "admin"}

# ---------------------- 3. 权限依赖 ----------------------
def admin_check(user: dict = Depends(get_current_user)):
    if user["role"] != "admin":
        raise HTTPException(403, "权限不足")
    return user

# ---------------------- 测试接口 ----------------------
@app.get("/list")
def test_list(pager: dict = Depends(page_params)):
    return {"code": 200, "pager": pager}

@app.get("/profile")
def profile(user: dict = Depends(get_current_user)):
    return {"code": 200, "user": user}

@app.get("/admin", dependencies=[Depends(admin_check)])
def admin_api():
    return {"code": 200, "msg": "管理员接口访问成功"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

实际运行结果

启动服务后访问 /docs,FastAPI 自动生成接口文档,4 个接口一目了然:

接口实测返回(/list 默认分页、size=200 被限制为 100、/profile 无 Token 返回 401):

GET /list                        => 200 {"code":200,"pager":{"page":1,"size":10}}
GET /list?page=3&size=200        => 200 {"code":200,"pager":{"page":3,"size":100}}
GET /page/data?page=2&size=20    => 200 {"code":200,"page":2,"size":20,"offset":20}
GET /profile(带Token)          => 200 {"code":200,"user":{"user_id":1001,"username":"管理员","role":"admin"}}
GET /profile(无Token)          => 401 {"detail":"未登录"}
GET /admin(带Token)            => 200 {"code":200,"msg":"管理员接口访问成功"}

可见:分页参数自动解析且 size 上限生效;无 Token 直接被 401 拦截,带 Token 才能拿到用户信息。


九、Day12 核心知识点总结

知识点说明
Depends 依赖注入抽离公共逻辑,代码解耦、复用
函数式依赖适合简单公共参数(分页、时间、筛选)
类依赖适合复杂封装、多工具方法场景
局部依赖单个接口独立鉴权
全局路由依赖整个模块统一鉴权拦截
依赖嵌套登录校验 + 权限拦截,双层组合

十、下期预告

Day13:JWT动态令牌鉴权实战

告别固定 Token!实现登录生成 JWT、过期刷新、加密校验、无状态鉴权,企业生产级登录体系!