一、代码结构解析
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()创建一个全局唯一的实例,所有地方共享同一个实例。
-
模块单例:
-
当其他文件(如
api.py)通过from .controllers.dept import dept_controller导入时 -
整个应用生命周期内只会存在一个
DeptController实例
-
-
使用示例:
# 在 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)
- 线程安全:
-
FastAPI 是基于 ASGI 的异步框架
-
由于没有共享状态修改,这种单例模式在异步环境下是安全的
-
控制器方法都使用
await进行异步操作
-
六、新人常见疑问解答
Q1:为什么不直接实例化,而要用单例?
-
统一管理:避免重复创建对象浪费资源
-
依赖管理:方便在 FastAPI 的依赖注入系统中使用
Q2:如果多个请求同时访问会有问题吗?
-
异步安全:FastAPI 的异步特性会保证每个请求独立处理
-
数据库安全:Tortoise ORM 的连接池会自动管理数据库连接
Q3:如何扩展这个控制器?
# 添加新方法示例
class DeptController(CRUDBase[...]):
async def get_dept_stats(self):
# 添加新的业务方法
return await self.model.filter(...)
七、开发技巧总结
-
Schema 校验:充分利用 Pydantic Schema 进行数据校验
-
DRY 原则:通用逻辑放在基类,特殊逻辑在子类实现
-
事务边界:涉及多个数据库操作时一定要用
@atomic() -
树形结构:小数据量用递归内存处理,大数据量考虑专用数据库方案
-
调试技巧:在
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:高效分页实现
-
分页公式:
(page-1)*page_size处理页码偏移 -
使用
Q对象构建复杂查询条件 -
分离 count 查询和数据查询
-
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:安全更新策略
-
exclude_unset=True只处理客户端实际提交的字段 -
显式排除 id 字段防止篡改
-
先查询后更新保证对象存在
-
update_from_dict自动处理字段更新
async def remove(self, id: int) -> None:
obj = await self.get(id=id)
await obj.delete()
技巧 9:级联删除处理
-
实际项目中需要根据模型关系配置删除策略
-
建议使用软删除(添加 is_deleted 字段)代替物理删除
架构设计思想:
-
单一职责原则:每个方法只完成一个特定功能
-
DRY 原则(Don't Repeat Yourself):通过泛型减少重复代码
-
依赖倒置:高层模块不依赖具体模型实现
-
异步架构:全面使用 async/await 提升并发能力
新手指南:
-
创建具体业务的 CRUD 类示例:
class UserCRUD(CRUDBase[User, UserCreateSchema, UserUpdateSchema]):
def __init__(self):
super().__init__(User)
-
在 FastAPI 路由中的典型用法:
@router.get("/{user_id}")
async def get_user(user_id: int):
return await UserCRUD().get(user_id)
-
复杂查询示例:
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)
常见陷阱:
-
N+1 查询问题:关联查询时记得使用
prefetch_related -
事务处理:多个操作需要包裹在事务中
-
分页性能:大数据量时需要游标分页代替偏移分页
-
类型转换:注意 Tortoise 模型与 Pydantic 模型的区别
建议在实际开发中补充:
-
权限校验装饰器
-
操作日志记录
-
缓存机制
-
数据预检钩子(pre-hook)
-
更完善的错误处理
通过这种通用 CRUD 基类的设计,可以快速实现 80% 的典型管理后台功能,同时保持代码的一致性和可维护性。
2152

被折叠的 条评论
为什么被折叠?



