欢迎光临
我们一直在努力

前后端开发基础:FastAPI数据库操作指南

1.后端

1.文件

  • api下的是每个文件的路由 可以自定义
  • models 是模型 每个接口都需要模型支撑
  • schemas 请求体文件 我们添加修改的请求体写在这里面要与模型字段一致
  • services 接口方法 我们将接口分为了路由和接口
  • config 配置文件 数据库配置 jwt redis 写在里面
  • main 主路由 主要用来调用api中的子路由 以及跨域 其他
  • migrations 数据迁移的文件
  • requirements.txt 依赖文件夹 运行可下载文本中的所有依赖
  • 2.配置

    # app/core/database.py
    """
    数据库配置文件
    这个文件定义了 Tortoise-ORM 连接 MySQL 数据库所需的所有配置信息
    """
    # from app.config.settings import settings

    # TORTOISE_ORM 是 Tortoise-ORM 规定的配置字典变量名
    # 后面用 register_tortoise 或 Aerich 时都会引用这个字典
    TORTOISE_ORM = {
    # 1. 连接配置 —— 定义数据库连接信息
    "connections": {
    # "default" 是默认连接的名字,必须有一个 default
    "default": {
    # engine:指定数据库后端引擎,MySQL 使用 tortoise.backends.mysql
    "engine": "tortoise.backends.mysql",

    # credentials:数据库连接凭证,包含主机、端口、用户名、密码等
    "credentials": {
    "host": "127.0.0.1", # MySQL 服务器地址
    "port": 3306, # MySQL 端口,默认 3306
    "user": "root", # 数据库用户名
    "password": "123456", # 数据库密码(请根据实际情况修改)
    "database": "Fast_api_shx", # 数据库名称
    "minsize": 1, # 连接池最小连接数
    "maxsize": 5, # 连接池最大连接数
    "charset": "utf8mb4", # 字符集,支持 emoji
    "echo": True # 是否打印 SQL 语句(开发环境建议开启)
    }
    }
    },

    # 2. 应用配置 —— 指定模型所在的模块
    "apps": {
    # "models" 是应用的名字,可以自定义,但 Aerich 需要使用这个名字
    "models": {
    # models 列表:指定包含 Tortoise 模型类的 Python 模块路径
    # "aerich.models" 是 Aerich 的内置模型,用于记录迁移历史,必须包含
    "models": ["models.models", "aerich.models"],

    # default_connection:指定这个应用使用哪个数据库连接
    "default_connection": "default",
    }
    },

    # 3. 时区配置
    "use_tz": False, # 是否使用时区
    "timezone": "Asia/Shanghai", # 时区设置
    "echo": True # ✅ 关键:打开 SQL 打印
    }

    我们用来连接数据库的配置 

    迁移命令 

    第一次 :aerich init -t config.TORTOISE_ORM  ,aerich init-db   

    后续     :aerich migrate   ,aerich upgrade 

    迁移后就可以调用mysql数据库中的数据了

    3.接口

    models模型 简单模型创建

    from tortoise import fields, models

    class Category(models.Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    order = fields.IntField()
    class Meta:
    table = "category"

    class Book(models.Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    price = fields.DecimalField(max_digits=10, decimal_places=2)
    numbers= fields.IntField(default=0)
    category = fields.ForeignKeyField("models.Category", related_name="books")
    class Meta:
    table = "book"

    1.查询所有 分页

    @book_api.get("/", summary="分页查询图书列表")
    async def get_book_list(page: int = 1, page_size: int = 10):
    """
    根据页码和每页条数分页查询图书数据
    page:当前页码,默认值1
    page_size:单页展示数据条数,默认值10
    返回内容:当前页码、每页条数、当前页量、总数据量、当前页图书数
    """
    book, total, count_total = await BookService.get_book_list(page, page_size)
    return {
    "page": page,
    "page_size": page_size,
    "total": total,
    "count_total": count_total,
    "data": book
    }

    class BookService:
    # 分页查询图书列表
    @staticmethod
    async def get_book_list(page, page_size):
    """分页查询图书"""
    # 计算数据偏移量
    offset = (page – 1) * page_size
    # 分页查询,同时预加载关联分类信息,避免多次查库
    booke = await Book.all().prefetch_related("category").offset(offset).limit(page_size)
    # 整理返回前端需要的字段
    booke_list = [
    {
    "id": book.id,
    "name": book.name,
    "price": book.price,
    "numbers": book.numbers,
    "category_id": book.category.id,
    "category": book.category.name
    } for book in booke
    ]
    total = len(booke_list)
    # 查询数据库全部图书总数量
    count_total = await Book.all().count()
    return booke_list, total, count_total

    从前端接受要分页的page和page_size 如果没有就是默认第一页的10条数据如果有传到service

    offset = (page – 1) * page_size 计算偏移量 然后分页查询

    定义book_list将数据加入 然后下面是分类的链接表 返回查询列表 总数量 当前查询数量

    2.查询指定数据

    @book_api.get("/{book_id}", summary="根据ID查询图书详情")
    async def get_book_detail(book_id: int):
    """
    依据图书id查询单本图书完整信息
    book_id:图书主键id,路径参数
    返回:包含状态、提示、图书详情
    """
    return await BookService.get_detail_by_id(book_id)

    @staticmethod
    async def get_detail_by_id(book_id: int):
    """根据ID查询图书详情"""
    try:
    # 根据id查图书,顺带查出分类名称
    book = await Book.get(id=book_id).prefetch_related("category")
    return {
    "code": 200,
    "msg": "查询成功",
    "data": {
    "id": book.id,
    "name": book.name,
    "price": book.price,
    "numbers": book.numbers,
    "category_id": book.category.id,
    "category": book.category.name
    }
    }
    except DoesNotExist:
    # 不存在
    return {"code": 400, "msg": "图书不存在", "data": None}

    从前端返回要查询的book_id 然后传到接口 进行查询 如果book_id存在就返回信息 如果不再就返回不存在的提示信息就可以了

    3.添加数据

    @book_api.post("/", summary="新增图书")
    async def add_book(form: BookCreateSchema):
    """
    新增一本图书,请求体BookCreateSchema做字段校验,所有字段必须全部传入
    form:前端提交的图书表单信息
    返回:新增成功/失败的状态提示
    """
    return await BookService.create_book(form.model_dump())

    @staticmethod
    async def create_book(info: dict):
    """新增图书入库"""
    await Book.create(**info)
    return {"code": 200, "msg": "新增成功"}

    请求体

    from pydantic import BaseModel

    class BookCreateSchema(BaseModel):
    name: str
    price: float
    numbers: int
    category_id: int

    class BookUpdateSchema(BaseModel):
    name: str | None = None
    price: float | None = None
    numbers: int | None = None
    category_id: int | None = None

    将前端返回添加的信息 直接添加到数据库中 

    4.修改数据

    @book_api.put("/{book_id}", summary="编辑修改图书")
    async def edit_book(book_id: int, form: BookUpdateSchema):
    """
    根据图书id修改图书信息,支持局部更新,不传的字段不会改动
    book_id:需要修改的图书id,路径参数
    form:前端提交的修改字段,字段全部可选
    返回:修改成功或失败提示
    """
    return await BookService.update_book(book_id, form.model_dump())

    @staticmethod
    async def update_book(book_id: int, info: dict):
    """修改图书,自动过滤None空字段,只更新传入的参数"""
    update_info = {k: v for k, v in info.items() if v is not None}
    update_count = await Book.filter(id=book_id).update(**update_info)
    if update_count == 0:
    return {"code": 400, "msg": "图书不存在,修改失败"}
    return {"code": 200, "msg": "修改成功"}

    从前端返回两个值 一个是要修改的id另外一个是修改的数据 在请求体中 都可以为空None 先判断修改的数据是否存在 在判断我们要修改的数据都有哪些然后1进行update修改

    5.删除数据

    @book_api.delete("/{book_id}", summary="删除图书")
    async def delete_book(book_id: int):
    """
    根据图书id删除对应图书数据
    book_id:待删除图书主键id
    返回:删除成功/失败提示
    """
    return await BookService.delete_book(book_id)

    @staticmethod
    async def delete_book(book_id: int):
    """根据ID删除图书"""
    del_count = await Book.filter(id=book_id).delete()
    if del_count == 0:
    return {"code": 400, "msg": "图书不存在,删除失败"}
    return {"code": 200, "msg": "删除成功"}

    传要删除的数据id判断是否存在然后删除

    2.前端

    vite.config.js进行配置

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import { resolve } from 'path'

    export default defineConfig({
    plugins: [vue()],
    resolve: {
    alias: {
    '@': resolve(__dirname, 'src')
    }
    },
    server: {
    proxy: {
    '/api': {
    target: 'http://127.0.0.1:8000',
    changeOrigin: true,
    }
    }
    }
    })

    然后是app页面

    <template>
    <div style="padding: 24px">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
    <h2>图书列表</h2>
    <el-button type="primary" @click="openAdd">新增图书</el-button>
    </div>
    <el-table :data="tableData" border style="width: 100%; margin: 20px 0">
    <el-table-column fixed prop="id" label="图书ID" width="100" />
    <el-table-column prop="name" label="书名" width="220" />
    <el-table-column prop="price" label="价格" width="120" />
    <el-table-column prop="numbers" label="库存数量" width="120" />
    <el-table-column prop="category_id" label="分类ID" width="120" />
    <el-table-column prop="category" label="分类名称" width="180" />
    <el-table-column fixed="right" label="操作" min-width="200">
    <template #default="scope">
    <el-button link type="primary" size="small" @click="handleDetail(scope.row)">详情</el-button>
    <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
    <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
    </template>
    </el-table-column>
    </el-table>
    <div style="display:flex;justify-content:flex-end">
    <el-pagination
    v-model:current-page="query.page"
    v-model:page-size="query.page_size"
    :page-sizes="[5,10,20,50]"
    background
    layout="total, sizes, prev, pager, next, jumper"
    :total="totalNum"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
    />
    </div>

    <!– 新增、编辑共用弹窗 –>
    <el-dialog v-model="dialogVisible" title="图书表单" width="520px">
    <el-form label-width="90px">
    <el-form-item label="书名">
    <el-input v-model="form.name"></el-input>
    </el-form-item>
    <el-form-item label="价格">
    <el-input v-model.number="form.price"></el-input>
    </el-form-item>
    <el-form-item label="库存数量">
    <el-input v-model.number="form.numbers"></el-input>
    </el-form-item>
    <!– 分类下拉选择 –>
    <el-form-item label="分类">
    <el-select v-model="form.category_id" placeholder="请选择分类" style="width: 100%">
    <el-option label="文学小说" value="1" />
    <el-option label="计算机编程" value="2" />
    <el-option label="历史人文" value="3" />
    <el-option label="心理学" value="4" />
    <el-option label="美食菜谱" value="5" />
    </el-select>
    </el-form-item>
    </el-form>
    <template #footer>
    <el-button @click="dialogVisible = false">取消</el-button>
    <el-button type="primary" @click="submitForm">确定提交</el-button>
    </template>
    </el-dialog>

    <!– 详情弹窗,样式和编辑弹窗完全一致 –>
    <el-dialog v-model="detailDialogVisible" title="图书详情" width="520px">
    <el-form label-width="90px">
    <el-form-item label="书名">
    <el-input v-model="detailForm.name" disabled></el-input>
    </el-form-item>
    <el-form-item label="价格">
    <el-input v-model.number="detailForm.price" disabled></el-input>
    </el-form-item>
    <el-form-item label="库存数量">
    <el-input v-model.number="detailForm.numbers" disabled></el-input>
    </el-form-item>
    <el-form-item label="分类">
    <el-input v-model="detailForm.category" disabled></el-input>
    </el-form-item>
    </el-form>
    <template #footer>
    <el-button @click="detailDialogVisible = false">关闭</el-button>
    </template>
    </el-dialog>
    </div>
    </template>

    <script setup>
    import { ref, onMounted } from 'vue'
    import axios from 'axios'
    import { ElMessage, ElMessageBox } from 'element-plus'

    const totalNum = ref(0)
    const tableData = ref([])

    const query = ref({
    page: 1,
    page_size: 5
    })

    // 新增编辑弹窗控制
    const dialogVisible = ref(false)
    // 区分新增/编辑模式 add新增 edit编辑
    const operateMode = ref('add')
    // 编辑行id
    const editBookId = ref(null)
    // 新增编辑表单
    const form = ref({
    name: '',
    price: 0,
    numbers: 0,
    category_id: ''
    })

    // 详情弹窗控制
    const detailDialogVisible = ref(false)
    // 详情只读表单
    const detailForm = ref({
    name: '',
    price: 0,
    numbers: 0,
    category_id: '',
    category: ''
    })

    // 加载图书列表 1.在挂载时加载
    // const loadBook = () => {
    // axios.get(`/api/book/?page=${query.value.page}&page_size=${query.value.page_size}`)
    // .then(res => {
    // console.log('后端返回', res.data)
    // tableData.value = res.data.data
    // totalNum.value = res.data.count_total
    // })
    // .catch(err => console.error(err))
    // }
    // 加载图书列表 2.在事件中加载
    const loadBook = () => {
    axios.get('/api/book/', {params: query.value})
    .then(res => {
    console.log(res.data)
    tableData.value = res.data.data
    totalNum.value = res.data.count_total
    })
    .catch(err => console.error(err))
    }

    // 每页条数切换
    const handleSizeChange = (newSize) => {
    query.value.page_size = newSize
    query.value.page = 1
    loadBook()
    }

    // 页码切换
    const handleCurrentChange = (newPage) => {
    query.value.page = newPage
    loadBook()
    }

    // 详情:调用接口打开弹窗,全部.then写法
    const handleDetail = (row) => {
    console.log('详情数据', row)
    axios.get(`/api/book/${row.id}`)
    .then(res => {
    console.log('详情完整数据', res.data)
    // 回填详情表单
    detailForm.value = res.data.data
    // 打开详情弹窗
    detailDialogVisible.value = true
    })
    .catch(err => {
    console.error('详情查询失败', err)
    ElMessage.error('详情加载失败')
    })
    }

    // 编辑
    const handleEdit = (row) => {
    console.log('编辑当前行', row)
    // 打开弹窗回填数据
    operateMode.value = 'edit'
    editBookId.value = row.id
    form.value = {
    name: row.name,
    price: row.price,
    numbers: row.numbers,
    category_id: row.category_id
    }
    dialogVisible.value = true
    }

    // 打开新增弹窗
    const openAdd = () => {
    operateMode.value = 'add'
    editBookId.value = null
    // 清空表单
    form.value = {
    name: '',
    price: 0,
    numbers: 0,
    category_id: ''
    }
    dialogVisible.value = true
    }

    // 提交新增/编辑表单
    const submitForm = () => {
    if (operateMode.value === 'add') {
    // 新增
    axios.post('/api/book/', form.value)
    .then(() => {
    ElMessage.success('新增成功')
    dialogVisible.value = false
    loadBook()
    })
    .catch(err => {
    ElMessage.error('新增失败')
    console.error(err)
    })
    } else {
    // 编辑
    axios.put(`/api/book/${editBookId.value}`, form.value)
    .then(() => {
    ElMessage.success('修改成功')
    dialogVisible.value = false
    loadBook()
    })
    .catch(err => {
    ElMessage.error('修改失败')
    console.error(err)
    })
    }
    }

    // 删除按钮事件 全部then写法
    const handleDelete = (row) => {
    ElMessageBox.confirm('确认删除当前图书?删除不可恢复', '删除提示', { type: 'warning' })
    .then(() => {
    return axios.delete(`/api/book/${row.id}`)
    })
    .then(() => {
    ElMessage.success('删除完成')
    loadBook()
    })
    .catch(() => {
    ElMessage.info('已取消删除')
    })
    }

    onMounted(loadBook)
    </script>

    赞(0)
    未经允许不得转载:171主机测评 » 前后端开发基础:FastAPI数据库操作指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址