用自定义装饰器给 FastAPI 接口加权限控制,代码量减一半

大家好,今天我们来聊一个在 Python 和 FastAPI 开发中既基础又强大的工具:装饰器 (Decorator)

很多使用 FastAPI 的朋友对装饰器肯定不陌生。我们每天都在用它来定义路由:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

这里的 @app.get("/") 就是一个装饰器。它把一个普通的 Python 函数 read_root 变成了一个处理 HTTP GET 请求的 API 端点。它很神奇,也很好用。但除了框架提供给我们的这些,我们能不能创建自己的装饰器来解决特定问题,让代码变得更优雅、更易于维护呢?

答案是肯定的。今天,我们就从一个常见的场景出发,一步步构建一个实用的自定义装饰器。

场景:接口权限控制

假设我们正在开发一个后台管理系统。系统里有不同角色的用户:普通用户、管理员、超级管理员。某些敏感接口,比如“删除用户”,我们只希望“超级管理员”才能调用。

最直接的想法可能是在接口函数内部做判断:

from fastapi import Depends, HTTPException, status

# 伪代码:假设我们有一个函数可以从 Token 中获取当前用户信息
async def get_current_user(token: str):
    # ... 实现获取用户的逻辑 ...
    # 返回一个包含角色信息 User 对象
    return current_user

@app.delete("/users/{user_id}")
async def delete_user(user_id: int, current_user: User = Depends(get_current_user)):
    # 权限检查逻辑
    if "super_admin" not in current_user.roles:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="对不起,你没有权限执行此操作",
        )

    # 真正的业务逻辑
    # ... 删除用户的代码 ...
    return {"message": "用户删除成功"}

这个实现方式完全没问题。但设想一下,如果系统里有十几个甚至几十个接口都需要“超级管理员”权限,我们是不是要在每个接口里都重复写一遍这段 if 判断逻辑?这显然违反了 DRY (Don’t Repeat Yourself) 原则,不仅啰嗦,而且未来如果权限逻辑需要变更(比如增加一个“审计员”角色也能访问),我们就得去修改所有相关的接口,非常容易出错。

这时候,就该轮到自定义装饰器出场了。

动手:创建一个权限检查装饰器

我们的目标是创建一个 @require_role('super_admin') 装饰器,把权限检查的逻辑从业务代码中抽离出来。

首先,我们得理解装饰器在 Python 中到底是什么。简单来说,装饰器就是一个接收函数作为参数,并返回一个新函数的函数。它允许我们在不修改原函数代码的情况下,为函数增加额外的功能。

让我们来构建这个权限装饰器。在 FastAPI 中,因为大量使用依赖注入(Dependency Injection),我们的装饰器需要巧妙地与之结合。

# 我们可以把这个文件放在例如 src/core/security.py
from functools import wraps
from fastapi import Depends, HTTPException, status
from .dependency import get_current_user # 假设从这里导入
from ..models.admin import User # 假设用户模型在这里

def require_role(required_role: str):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # 关键点:如何在装饰器内部拿到依赖注入的结果?
            # FastAPI 的路由函数参数是通过 kwargs 传递给装饰器的
            # 但依赖注入的参数在调用时才解析,这里直接拿不到
            # 所以我们需要找到 current_user
            
            # 一个聪明的做法是,让被装饰的函数签名中必须包含 current_user
            # FastAPI 会在调用 wrapper 前解析依赖
            # 但更通用的做法是,在装饰器内部也使用 Depends
            # 不过为了简化,我们先假设依赖已经被解析并存在于 kwargs 中
            
            # 让我们换一种更 FastAPI-style 的思路
            # 创建一个返回依赖的依赖项
            
            # 最终,最优雅的方式是创建一个返回函数的函数
            pass # 先跳过实现,我们看下面更优雅的方式

        return wrapper
    return decorator

上面的思路有点复杂。在 FastAPI 的世界里,我们可以利用 Depends 来让事情变得更简单。我们可以创建一个“依赖项类”,它本身可以像函数一样被调用。

一个更清晰、更符合 FastAPI 风格的实现如下:

# 我们可以把这个文件放在例如 src/core/permissions.py
from functools import wraps
from fastapi import Depends, HTTPException, status
from ..models.admin import User # 假设用户模型
from .dependency import get_current_user # 假设获取当前用户的依赖

def require_permission(permission: str):
    """
    创建一个依赖项,该依赖项会检查用户是否具有所需权限。
    """
    def permission_checker(current_user: User = Depends(get_current_user)):
        if not hasattr(current_user, 'permissions') or permission not in current_user.permissions:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"权限不足,需要 '{permission}' 权限",
            )
        return current_user
    
    return Depends(permission_checker)

使用这种方式,我们可以这样改造我们的接口:

from fastapi import APIRouter
from ..core.permissions import require_permission

router = APIRouter()

@router.delete(
    "/users/{user_id}",
    dependencies=[require_permission("user:delete")] # 使用 dependencies 参数
)
async def delete_user(user_id: int):
    # 业务逻辑
    # ...
    return {"message": "用户删除成功"}

看,现在 delete_user 函数变得多么干净!它只关心自己的核心业务逻辑。所有关于权限检查的复杂性都被封装在了 require_permission 中。如果未来权限逻辑需要修改,我们只需要更新这一个地方。

为什么这很重要?

这种做法不仅仅是代码量的减少,它体现了一种更优秀的设计思想——关注点分离 (Separation of Concerns)

  • API 路由函数:只关心接收请求、调用业务服务、返回响应。
  • 权限装饰器:只关心验证用户身份和权限。
  • 业务服务:只关心核心的业务逻辑(比如数据库操作)。

每一部分都只做一件事,并把它做好。

推广时间:我的 FastAPI 模板项目

在实际项目中,类似这样的代码组织和设计模式是提升项目质量和可维护性的关键。这也是我创建 FastAPI-Template 的初衷。

我的这个模板项目已经为大家搭建好了一个健壮的、可扩展的应用骨架。它不仅包含了:

  • 清晰的项目结构划分 (api, core, services, models 等)。
  • 开箱即用的用户认证和 JWT 支持。
  • 数据库集成和模型管理。
  • 统一的日志和异常处理。

更重要的是,它鼓励像我们今天讨论的这种最佳实践。你可以很容易地在 src/core/ 目录下添加像 permissions.py 这样的模块来扩展系统功能,而不会让代码变得混乱。它为你处理了所有繁琐的“样板代码”,让你能从第一天起就专注于实现有价值的业务功能。

总结

装饰器是 Python 提供的一个强大工具。在 FastAPI 中,除了使用框架内置的装饰器,学会根据业务场景创建自己的装饰器(或类似的依赖项),是每一位进阶开发者的必经之路。它能极大地提升代码的可读性、可重用性和可维护性。

如果你正在寻找一个能帮你实践这些优秀思想的 FastAPI 项目起点,不妨看看我的 FastAPI-Template

希望今天的分享对你有所启发!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值