控制器controller的实现方式和用法

一、代码结构解析

1. 类继承关系
class DeptController(CRUDBase[Dept, DeptCreate, DeptUpdate]):
  • 技巧:这里使用了模板方法模式,CRUDBase 是一个封装了基础增删改查操作的基类

  • 好处:子类只需要关注业务逻辑的特殊部分,不用重复编写基础 CRUD 代码

  • 模板参数Dept(模型)、DeptCreate(创建校验 Schema)、DeptUpdate(更新校验 Schema)

2. 初始化方法
def __init__(self):
    super().__init__(model=Dept)
  • 作用:显式指定操作的模型为 Dept

  • 技巧:通过继承可以灵活更换模型,比如创建 UserController 时只需继承并修改模型


二、核心方法解析

1. 部门树结构生成
async def get_dept_tree(self, name):
    # [代码略]
  • 递归技巧:使用 build_tree 内部函数递归构建树形结构

  • 查询优化
    • Q(is_deleted=False) 实现软删除过滤

    • name__contains 实现模糊查询

  • 性能注意一次性查询全部数据到内存,适合数据量小的场景。如果部门数量大(>1000),建议分页或使用其他树结构存储方案

2. 闭包表操作
async def update_dept_closure(...):
  • 闭包表概念:通过 DeptClosure 表存储部门层级关系(祖先-后代-层级)

  • 典型操作
    • 插入新部门时:继承父部门的所有祖先关系 + 添加自身关系

    • 修改父部门时:删除旧关系 + 重建新关系

  • 技巧bulk_create 批量创建效率远高于逐条创建


三、事务处理

@atomic()
async def create_dept(...):
  • 事务必要性:创建部门时需要同时操作部门表和闭包表,必须保证原子性

  • 装饰器优势@atomic() 会自动开启事务,方法内所有数据库操作要么全部成功,要么全部回滚

  • 错误处理:如果方法中抛出异常,所有数据库修改都会自动回滚


四、软删除实现

obj.is_deleted = True
await obj.save()
  • 优势:保留历史数据,避免物理删除导致数据丢失

  • 配套措施:所有查询都需默认过滤 is_deleted=False

  • 扩展技巧:可以增加 deleted_at 字段记录删除时间


五、对象使用方式

问题解答:dept_controller = DeptController() 可以被其他文件直接导入使用吗?

答案是肯定的,这是 Python 模块系统的特性:

单例模式:全局唯一实例
  • 问题:如果每次调用都创建一个新实例,可能导致资源浪费(如重复连接数据库)或状态不一致。

  • 解决:通过dept_controller = DeptController() 创建一个全局唯一的实例,所有地方共享同一个实例。

  1. 模块单例

    • 当其他文件(如 api.py)通过 from .controllers.dept import dept_controller 导入时

    • 整个应用生命周期内只会存在一个 DeptController 实例

  2. 使用示例

# 在 api.py 中
from fastapi import APIRouter
from .controllers.dept import dept_controller

router = APIRouter()

@router.post("/depts")
async def create_dept(data: DeptCreate):
    return await dept_controller.create_dept(data)
  1. 线程安全
    • FastAPI 是基于 ASGI 的异步框架

    • 由于没有共享状态修改,这种单例模式在异步环境下是安全的

    • 控制器方法都使用 await 进行异步操作


六、新人常见疑问解答

Q1:为什么不直接实例化,而要用单例?
  • 统一管理:避免重复创建对象浪费资源

  • 依赖管理:方便在 FastAPI 的依赖注入系统中使用

Q2:如果多个请求同时访问会有问题吗?
  • 异步安全:FastAPI 的异步特性会保证每个请求独立处理

  • 数据库安全:Tortoise ORM 的连接池会自动管理数据库连接

Q3:如何扩展这个控制器?
# 添加新方法示例
class DeptController(CRUDBase[...]):
    async def get_dept_stats(self):
        # 添加新的业务方法
        return await self.model.filter(...)

七、开发技巧总结

  1. Schema 校验:充分利用 Pydantic Schema 进行数据校验

  2. DRY 原则:通用逻辑放在基类,特殊逻辑在子类实现

  3. 事务边界:涉及多个数据库操作时一定要用 @atomic()

  4. 树形结构:小数据量用递归内存处理,大数据量考虑专用数据库方案

  5. 调试技巧:在 update_dept_closure 中添加 print 语句观察闭包表变化

八、技巧分析

from typing import Any, Dict, Generic, List, NewType, Tuple, Type, TypeVar, Union
from pydantic import BaseModel
from tortoise.expressions import Q
from tortoise.models import Model

技巧 1:导入语句组织规范

  • 标准库模块在前,第三方模块在后

  • 类型提示相关导入优先

  • 使用明确的导入方式(避免通配符导入)

Total = NewType("Total", int)
ModelType = TypeVar("ModelType", bound=Model)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)

技巧 2:类型安全设计

  • NewType 创建语义化类型(Total 比单纯 int 更易理解)

  • TypeVar 定义泛型类型参数,提高代码复用性

  • bound 参数约束类型范围(确保类型安全性)

class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, model: Type[ModelType]):
        self.model = model

技巧 3:泛型类的应用

  • 通过继承 Generic 创建通用基类

  • 子类继承时可指定具体类型(如 CRUDUser

  • 适用于所有模型的基础 CRUD 操作

    async def get(self, id: int) -> ModelType:
        return await self.model.get(id=id)

技巧 4:基础查询方法

  • 使用 ORM 的 get 方法直接获取对象

  • 注意:实际开发中需要处理 DoesNotExist 异常

    async def list(self, page: int, page_size: int, 
                 search: Q = Q(), order: list = []) -> Tuple[Total, List[ModelType]]:
        query = self.model.filter(search)
        return await query.count(), await query.offset(
            (page - 1) * page_size).limit(page_size).order_by(*order)

技巧 5:高效分页实现

  1. 分页公式:(page-1)*page_size 处理页码偏移

  2. 使用 Q 对象构建复杂查询条件

  3. 分离 count 查询和数据查询

  4. order_by(*order) 支持多字段排序(如 ["name", "-created_at"])

技巧 6:查询优化

  • 链式调用保持查询可读性

  • 先构建基础查询再添加分页参数

  • 使用异步 await 提升并发性能

    async def create(self, obj_in: CreateSchemaType) -> ModelType:
        if isinstance(obj_in, Dict):
            obj_dict = obj_in
        else:
            obj_dict = obj_in.model_dump()
        obj = self.model(**obj_dict)
        await obj.save()
        return obj

技巧 7:灵活的参数处理

  • 同时支持 Pydantic 模型和原生字典

  • model_dump() 将 Pydantic 对象转为字典

  • ** 解包操作自动匹配模型字段

    async def update(self, id: int, obj_in: Union[UpdateSchemaType, Dict[str, Any]]) -> ModelType:
        if isinstance(obj_in, Dict):
            obj_dict = obj_in
        else:
            obj_dict = obj_in.model_dump(exclude_unset=True, exclude={"id"})
        obj = await self.get(id=id)
        obj = obj.update_from_dict(obj_dict)
        await obj.save()
        return obj

技巧 8:安全更新策略

  1. exclude_unset=True 只处理客户端实际提交的字段

  2. 显式排除 id 字段防止篡改

  3. 先查询后更新保证对象存在

  4. update_from_dict 自动处理字段更新

    async def remove(self, id: int) -> None:
        obj = await self.get(id=id)
        await obj.delete()

技巧 9:级联删除处理

  • 实际项目中需要根据模型关系配置删除策略

  • 建议使用软删除(添加 is_deleted 字段)代替物理删除

架构设计思想

  1. 单一职责原则:每个方法只完成一个特定功能

  2. DRY 原则(Don't Repeat Yourself):通过泛型减少重复代码

  3. 依赖倒置:高层模块不依赖具体模型实现

  4. 异步架构:全面使用 async/await 提升并发能力

新手指南

  1. 创建具体业务的 CRUD 类示例:

class UserCRUD(CRUDBase[User, UserCreateSchema, UserUpdateSchema]):
    def __init__(self):
        super().__init__(User)
  1. 在 FastAPI 路由中的典型用法:

@router.get("/{user_id}")
async def get_user(user_id: int):
    return await UserCRUD().get(user_id)
  1. 复杂查询示例:

search = Q(name__icontains="张") | Q(email__icontains="example.com")
order = ["-created_at", "name"]
await UserCRUD().list(page=1, page_size=10, search=search, order=order)

常见陷阱

  1. N+1 查询问题:关联查询时记得使用 prefetch_related

  2. 事务处理:多个操作需要包裹在事务中

  3. 分页性能:大数据量时需要游标分页代替偏移分页

  4. 类型转换:注意 Tortoise 模型与 Pydantic 模型的区别

建议在实际开发中补充:

  1. 权限校验装饰器

  2. 操作日志记录

  3. 缓存机制

  4. 数据预检钩子(pre-hook)

  5. 更完善的错误处理

通过这种通用 CRUD 基类的设计,可以快速实现 80% 的典型管理后台功能,同时保持代码的一致性和可维护性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值