当前位置: 首页 > news >正文

企业级FastAPI后端模板搭建(四)数据库迁移

用Tortoise-ORM +Aerich实现数据库迁移自动化

安装必要依赖

pipinstalltortoise-orm aerich

补充说明:安装数据库驱动 pip install tortoise-orm[asyncodbc];Tortoise 当前支持以下数据库:SQLite (using aiosqlite)、PostgreSQL >= 9.4 (using asyncpg)、MySQL/MariaDB (using asyncmy)、Microsoft SQL Server/Oracle (using asyncodbc)

数据模型定义

创建models/base.py文件,代码如下:

fromtortoiseimportfields,modelsclassBaseModel(models.Model):id=fields.BigIntField(pk=True,index=True)classMeta:abstract=TrueclassOperatorMixin:create_by=fields.BigIntField(index=True,description='创建人ID',null=True)create_time=fields.DatetimeField(auto_now_add=True,index=True,null=True,description='创建时间')update_by=fields.BigIntField(index=True,description='修改人ID',null=True)update_time=fields.DatetimeField(auto_now=True,index=True,null=True,description='更新时间')

创建models/admin.py文件,代码如下:

fromtortoiseimportfieldsfrom.baseimportBaseModel,OperatorMixinfromschemas.menusimportMenuTypefrom.enumsimportMethodTypeclassUser(BaseModel,OperatorMixin):account=fields.CharField(description='账号',max_length=64,unique=True)username=fields.CharField(description='用户名称',max_length=64,null=True)gender=fields.SmallIntField(default=1,description='性别(1-男, 2-女, 0-保密)')password=fields.CharField(max_length=128,description='密码')avatar=fields.CharField(index=True,description='用户头像',max_length=255,null=True)mobile=fields.CharField(index=True,description='联系方式',max_length=20,null=True)email=fields.CharField(index=True,max_length=255,description='邮箱')status=fields.SmallIntField(default=1,description='状态(1-正常, 0-禁用)')is_superuser=fields.BooleanField(default=False,description='是否为超级管理员')last_login=fields.DatetimeField(null=True,description="最后登录时间",index=True)is_deleted=fields.SmallIntField(default=0,description='逻辑删除标识(0-未删除, 1-已删除)')dept=fields.ForeignKeyField('models.Dept',related_name="users",index=True,null=True,on_delete=fields.SET_NULL,description='部门ID')roles=fields.ManyToManyField("models.Role",related_name="users",through="sys_user_role",forward_key='user_id',backward_key='role_id')classMeta:table="sys_user"table_description="用户表"classDept(BaseModel,OperatorMixin):name=fields.CharField(max_length=100,unique=True,description="部门名称",index=True)code=fields.CharField(max_length=100,unique=True,description="部门编号",index=True)parent_id=fields.IntField(default=0,description="父节点ID",index=True)tree_path=fields.CharField(max_length=255,description="父节点ID路径",index=True)sort=fields.SmallIntField(default=0,description="显示顺序")status=fields.SmallIntField(default=1,description="状态(1-正常 0-禁用)")is_deleted=fields.SmallIntField(default=0,description='逻辑删除标识(1-已删除 0-未删除)')classMeta:table="sys_dept"table_description="部门表"classRole(BaseModel,OperatorMixin):name=fields.CharField(description='角色名称',max_length=64,unique=True)code=fields.CharField(description='角色编码',max_length=32,unique=True)sort=fields.IntField(description='显示顺序',index=True,null=True)status=fields.SmallIntField(default=1,description='角色状态(1-正常, 0-停用)')data_scope=fields.SmallIntField(index=True,description='数据权限(1-所有数据, 2-部门及子部门, 3-本部门, 4-本人)',null=True)desc=fields.CharField(max_length=500,null=True,description="角色描述")# 多对多关系menus=fields.ManyToManyField("models.Menu",related_name="roles",through="sys_role_menu",forward_key='role_id',backward_key='menu_id')apis=fields.ManyToManyField("models.Api",related_name="apis",through="sys_role_api",forward_key='role_id',backward_key='api_id')is_deleted=fields.SmallIntField(default=0,description='逻辑删除标识(0-未删除, 1-已删除)')classMeta:table="sys_role"table_description="角色表"classMenu(BaseModel,OperatorMixin):parent_id=fields.BigIntField(description='父菜单ID')tree_path=fields.CharField(max_length=255,description='父节点ID路径',null=True,index=True)title=fields.CharField(max_length=64,description='菜单名称')type=fields.CharEnumField(MenuType,null=True,description="菜单类型(C-目录 M-菜单 B-按钮)")name=fields.CharField(max_length=255,description='路由名称(Vue Router 中用于命名路由)',null=True,index=True)path=fields.CharField(max_length=128,description='路由路径(Vue Router 中定义的 URL 路径)',null=True,index=True)component=fields.CharField(max_length=128,description='组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue)',null=True,index=True)perm=fields.CharField(max_length=128,description='权限标识(按钮专用)',null=True,index=True)always_show=fields.SmallIntField(default=0,description='【目录】仅一个子路由时是否始终显示(1-是, 0-否)')keep_alive=fields.SmallIntField(default=0,description='【菜单】是否开启页面缓存(1-是, 0-否)')visible=fields.SmallIntField(default=1,description='显示状态(1-显示, 0-隐藏)')sort=fields.IntField(default=0,description='排序')icon=fields.CharField(max_length=64,description='菜单图标',null=True,index=True)redirect=fields.CharField(max_length=128,description='跳转路径',null=True,index=True)params=fields.CharField(max_length=255,description='路由参数',null=True,index=True)classMeta:table="sys_menu"table_description="菜单表"classApi(BaseModel,OperatorMixin):path=fields.CharField(max_length=100,description="API路径",index=True)method=fields.CharEnumField(MethodType,description="请求方法",index=True)summary=fields.CharField(max_length=500,description="请求简介",index=True)tags=fields.CharField(max_length=100,description="API标签",index=True)classMeta:table="sys_api"

创建models/enums.py文件,代码如下:

fromenumimportStrEnumclassMethodType(StrEnum):GET="GET"POST="POST"PUT="PUT"DELETE="DELETE"PATCH="PATCH"

创建schemas/menus.py文件,代码如下:

fromenumimportStrEnumclassMenuType(StrEnum):CATALOG="catalog"# 目录MENU="menu"# 菜单BUTTON='button',# 按钮EXTLINK='extlink',# 外链

创建models/__init__.py文件,代码如下:

from.adminimport*

代码解析

ForeignKeyField:定义外键

dept=fields.ForeignKeyField('models.Dept',related_name="users",index=True,null=True,on_delete=fields.SET_NULL,description='部门ID')
  • model_name‌:必填项,写明要关联的模型名称,支持字符串形式以便延迟加载 。
  • related_name‌:用于在关联模型上创建反向关系,方便从另一方查询当前模型的数据 。
  • on_delete‌:定义当被关联的数据被删除时的处理策略,默认是级联删除 。
    • ‌CASCADE‌:级联删除,关联模型被删,当前模型对应数据也跟着删 。
    • SET_NULL‌:置为空,关联模型被删,外键字段变为 NULL,需设置 null=True 。
    • RESTRICT‌:限制删除,只要有外键指向,就不允许删除关联模型 。
    • SET_DEFAULT‌:重置为默认值,关联模型被删,外键字段变为默认值,需设置 default 。‌‌‌‌
  • db_constraint‌:控制是否在数据库层面创建外键约束,默认开启以保证数据完整性 。‌‌‌

ManyToManyField:定义两个模型之间的多对多关系

roles=fields.ManyToManyField("models.Role",related_name="users",through="sys_user_role",forward_key='user_id',backward_key='role_id')
  • related_model(位置参数)‌:目标模型的字符串路径(如 “models.Role”),必须指定。
  • related_name‌:反向关系名,用于从目标模型访问此关系(如 related_name=“users”)。
  • through‌:自定义中间表名(字符串,如 “sys_user_role”);不指定时自动创建。
  • forward_key:指定中间表里指向“定义该字段的模型”的外键列名,默认自动生成为 {model_name}_id(小写)(例如,若模型是 User,则默认 forward_key=“user_id”)。
  • backward_key: 对应中间表中指向“目标模型”的外键列,默认为 {target_model_name}_id。

**CharEnumField **:用于数据库字段的字符串枚举类型

type=fields.CharEnumField(MenuType,null=True,description="菜单类型(C-目录 M-菜单 B-按钮)")
  • enum_type:枚举类
  • description:描述。若未指定,将自动设置为包含“名称: 值”对的多行列表。
  • max_length:长度。若为零,则会从enum_type自动检测。

注册模型

修改settings/config.py文件,添加如下代码:

@propertydefTORTOISE_ORM(self)->dict:# SQLite fallback configurationreturn{"connections":{"default":{"engine":"tortoise.backends.sqlite","credentials":{"file_path":"fastapi_backend.sqlite3"},}},"apps":{"models":{"models":["models","aerich.models"],"default_connection":"default",},},"use_tz":False,"timezone":"Asia/Shanghai",}

修改core/init_app.py文件,代码如下:

fromfastapiimportFastAPIfromapiimportapi_routerfromaerichimportCommandfromlogimportloggerfromsettings.configimportsettingsdefregister_routers(app:FastAPI,prefix:str="/api"):app.include_router(api_router,prefix=prefix)asyncdefinit_db():command=Command(tortoise_config=settings.TORTOISE_ORM)try:awaitcommand.init_db(safe=True)exceptFileExistsError:passawaitcommand.init()try:awaitcommand.migrate(no_input=True)exceptAttributeErrorase:logger.error(f"数据库迁移失败:{e}")logger.warning("请手动检查数据库和migrations状态")raiseRuntimeError("数据库迁移失败,请检查数据库连接和migrations状态")fromeawaitcommand.upgrade(run_in_transaction=True)asyncdefinit_data():logger.info("🚀 系统初始化开始...")logger.info("🔧 开始数据库初始化和迁移...")awaitinit_db()logger.info("✅ 数据库初始化完成")

修改main.py文件,代码如下:

fromfastapiimportFastAPIfromcontextlibimportasynccontextmanagerfromtortoiseimportTortoisefromcore.init_appimportregister_routers,init_data@asynccontextmanagerasyncdeflifespan(app:FastAPI):awaitinit_data()yieldawaitTortoise.close_connections()app=FastAPI(lifespan=lifespan)register_routers(app,prefix="/api")

执行uvicorn main:app运行项目

项目启动之后会自动生成迁移文件(文件在migrations/目录下面) 和 数据库文件(fastapi_backend.sqlite3),连接数据库之后可以看到如下图所示的表格:


其中,sys_role_apisys_role_menusys_user_role是通过ManyToManyField定义的多对多关系自动生成的。

http://www.jsqmd.com/news/1129048/

相关文章:

  • 3个简单步骤掌握VIA键盘配置:打造你的个性化机械键盘
  • 如何在Unreal Engine 5中实现专业级体积特效:OpenVDB与NanoVDB插件终极指南
  • PDF2Audio:将学术文档转化为有声内容的智能解决方案
  • CSS颜色
  • WGAN-GP 在 CPU 上训练插画的启示:从理论到受限资源下的生成实践
  • Codex插件使用指南:从下载到上手全流程 Codex插件、Codex客户端下载、Codex使用教程、AI插件使用、Codex Skill、MCP是什么、Codex插件安装
  • 【Atlas】Solr 在 Atlas 中的作用是什么?是否可以替换为 Elasticsearch?
  • IPATool终极实战:解锁iOS应用包下载与逆向分析的完整指南
  • 深度学习材料研发革命:如何用Python算法库构建智能设计系统?
  • 【技术管理者实战】两面三刀的下属,如何不动声色地请离?
  • 猫抓浏览器扩展:10个高效资源嗅探技巧完全指南
  • 小白也能懂的 RAG 原理 —— 从检索到生成的完整指南
  • 适合零基础搭建Agent的低代码工具平台
  • 5分钟构建AI浏览器自动化助手:Stagehand终极指南
  • 3分钟搞定Spotify音乐下载:spotDL完整指南与网页界面使用教程
  • 当前流行的OCR工具对比与技术选型
  • Cargo workspace 版本发布:多包项目别手动改到手酸
  • 第30章 类型系统高级话题
  • CISP-PTE渗透测试知识体系详解:从基础到实战的完整能力构建路径
  • C#视觉检测翻车实录:我把OK当成NG拒收,差点被产线大姐当场“祭天”
  • C#图像处理黑魔法:揭秘直方图均衡化,如何让模糊的“马赛克”秒变高清“写真”?
  • 5分钟掌握B站缓存视频转换技巧:m4s-converter完整使用指南
  • 怎样轻松实现移动端图片滑动浏览:3个实用技巧提升用户体验
  • DuMate智能体:DuMate 浏览器插件安装指南
  • 【Linux】九.进程概念--环境变量及其相关指令
  • 高效技巧怎么用 AI 做表格,搭配 AI 导出鸭一站式搞定表格生成与导出工作
  • 【Atlas】Atlas 的 Type System 是什么?它如何支撑元模型定义?
  • F3闪存检测工具:5分钟识别扩容盘欺诈的完整指南
  • luogu----P1000 超级玛丽游戏
  • 终极指南:如何用AI增强开发工作流实现3倍效率提升