前后端分离毕业设计实战:Django+Vue+MySQL图书馆管理系统开发全解析
发布时间:2026/9/28 5:35:51来源:尧图网络
简介基于PythonDjangoVueMySql前后端分离架构的图书馆管理系统毕业设计项目面向计算机相关专业毕业生及需要快速搭建管理类系统的开发者完整覆盖图书借阅、归还、罚金缴纳、留言板及后台管理等业务闭环。压缩包整体约65.38MB内含完整源代码、论文答辩材料、任务书和视频教程便于对照学习与二次开发。系统采用B/S架构包含管理员与用户双角色管理员可管理用户、图书分类、图书信息及借还流程并处理罚金、留言和系统配置用户端支持图书借阅、归还、收藏及罚金查询前台首页提供图书信息浏览与公告反馈入口。结合模块代码可深入理解DjangoVue前后端分离项目的实际开发流程、MySQL数据库设计以及部署答辩要点视频教程还演示了运行配置和答辩呈现思路。目前已有148人学习下载适合作为毕设参考或项目实战练手资源。1. 毕业设计选“前后端分离的图书馆管理系统”这个题目值不值得做图书管理系统是高校毕设里出现频率最高的题目之一但大多数提交上去的作品还停留在 Django 模板渲染 原生 jQuery 的老路子。把题目限定成「Python Django Vue MySQL 的前后端分离架构」后同样的借阅管理业务难度直接从增删改查跳到了工程化项目后端只出 REST 接口前端独立部署数据落在 MySQL答辩时能讲的东西多了不止一倍。这个题目适合两类人一类是想拿高分、愿意花两个月把架构讲清楚的学生另一类是已经在培训班学过 Vue 或 Django 其中一端、想补齐另一端形成完整项目经验的初学者。它解决的问题也很直接图书馆里图书、读者、借阅、归还、超期这几条核心链路用一套前后端分离的方案跑通并且每一环节都能在论文里写出设计依据。下面按我自己的实现顺序从后端到数据库再到前端把每一步怎么做、参数怎么设、坑在哪讲完。2. Django 后端从空目录到可运行的借阅 REST 接口2.1 初始化工程与依赖选择先定版本再动手前后端分离的 Django 项目后端职责只有一个提供 JSON 接口。所以不用创建任何模板文件也不用管静态文件目录核心就三件事建工程、建 app、配 DRF。Python 环境建议直接用 3.8 以上的版本Django 选 LTS 系列。Windows 上我习惯用pymysql连 MySQLLinux 上换mysqlclient这样可以避开编译报错。# 创建虚拟环境并激活Windows 下激活命令不同 python -m venv venv source venv/bin/activate # 安装核心依赖版本不写死装当前 pip 能拿到的稳定版 pip install django djangorestframework django-cors-headers djangorestframework-simplejwt pymysql # 创建项目与 app django-admin startproject library_backend cd library_backend python manage.py startapp books依赖里四个包各管一件事DRF 提供序列化器和视图集省掉手写 JSON 解析django-cors-headers解决跨域simplejwt提供登录令牌pymysql是 MySQL 驱动。创建完 app 后顺手在项目settings.py里注册否则后面 migrate 的时候 models 里的表不会生成。注册完后还要做一个关键动作在__init__.py里声明用 pymysql否则 Django 默认找 MySQLdb 会直接报错找不到模块。# library_backend/__init__.py import pymysql pymysql.install_as_MySQLdb()这段代码的量级很小但漏掉它的后果是python manage.py migrate时连着报五六行 traceback属于典型的起步翻车。装完依赖后建议立刻跑一次python manage.py check确认环境没问题再往下写代码。2.2 数据模型与序列化器把借阅关系表达清楚图书馆管理系统的核心模型就三张表读者、图书、借阅记录。读者直接用 Django 自带的AbstractUser扩展加上学号和角色字段图书表存书名、ISBN、馆藏数量、可借数量借阅记录表关联读者和图书记借出日期、应还日期、实际归还日期。模型设计里最容易错的是外键的on_delete参数借阅记录关联图书用PROTECT防止图书被删时借阅记录变成孤儿关联读者也同理。# books/models.py from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): student_id models.CharField(max_length20, blankTrue) role models.CharField(max_length10, defaultstudent) # student/admin class Book(models.Model): title models.CharField(max_length200) isbn models.CharField(max_length20, uniqueTrue) total_count models.IntegerField(default1) available_count models.IntegerField(default1) def __str__(self): return self.title class BorrowRecord(models.Model): user models.ForeignKey(User, on_deletemodels.PROTECT) book models.ForeignKey(Book, on_deletemodels.PROTECT) borrow_date models.DateField(auto_now_addTrue) due_date models.DateField() return_date models.DateField(nullTrue, blankTrue) class Meta: ordering [-borrow_date]available_count是业务里的关键字段每次借书成功要减一还书要加一并发场景下需要在事务里处理。模型建好后执行makemigrations和migrate再写序列化器。DRF 的ModelSerializer能让序列化代码量降到最低但要注意借阅记录的序列化器里user和book默认输出的是外键 ID前端拿到 ID 还要再查一次书名。更省事的做法是嵌套序列化器直接返回书名和读者姓名代价是列表接口会多几次查询量级小无所谓。2.3 视图集、路由与 JWT 登录接口数量越少越好维护视图层我一般直接用viewsets.ModelViewSet一把梭借助 DRF 的路由注册器自动生成标准增删改查接口。图书接口给任何人可读但写操作只允许管理员借阅接口的核心是「借书」和「还书」两个自定义动作而不是标准 CRUD。借书的逻辑是校验读者没超期、校验书还有库存、创建借阅记录、扣减库存四步全部包在transaction.atomic()里。# books/views.py from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.db import transaction from .models import Book, BorrowRecord from .serializers import BookSerializer, BorrowRecordSerializer class BorrowViewSet(viewsets.ModelViewSet): queryset BorrowRecord.objects.all() serializer_class BorrowRecordSerializer action(detailTrue, methods[post]) def borrow(self, request, pkNone): book self.get_object() if book.available_count 1: return Response({detail: 库存不足}, statusstatus.HTTP_400_BAD_REQUEST) with transaction.atomic(): borrow BorrowRecord.objects.create( userrequest.user, bookbook, due_daterequest.data.get(due_date) ) book.available_count - 1 book.save() return Response(BorrowRecordSerializer(borrow).data)这里detailTrue表示这个动作作用在单本书上前端请求POST /api/books/{id}/borrow/就能触发。注意借阅记录里的user直接从request.user取前端不用传读者 ID这个设计能堵住「替别人借书」的越权漏洞。权限控制方面我参照若依那套 RBAC 的思路做了简化全局用IsAuthenticated保证必须登录管理员操作单独用IsAdminUser限定。登录接口不用自己写simplejwt 自带token_obtain_pair拿到 access token 后前端存起来每次请求带到Authorization: Bearer token头里就行。3. MySQL 里的图书馆建表、ORM 查询与演示数据3.1 建库配置字符集、连接参数和时区一次到位Django 项目连 MySQL 时最常踩的坑不是写代码而是建库时的字符集。MySQL 8 默认字符集其实是utf8mb4但如果用图形客户端手动建库时没选建出来的库可能是latin1中文存进去再读出来就变成乱码。我的习惯是建库语句里直接指定字符集和排序规则不依赖客户端默认值。CREATE DATABASE library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后在 Django 的settings.py里把数据库配置指过去。连接参数里值得调的是CONN_MAX_AGE它控制数据库连接复用时间默认是 0即每次请求都新建连接本地开发不明显答辩演示时连续点几次查询就能感觉到慢。DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: library_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: {charset: utf8mb4}, } }OPTIONS里指定charset是双保险保证 Django 和 MySQL 之间的连接也用 utf8mb4。时区设置同样要提前做Django 默认TIME_ZONE是UTC如果只改这一项不把USE_TZ关掉借阅记录的日期会跟本地差八小时。我的做法是TIME_ZONE Asia/Shanghai、USE_TZ True然后在数据库这一侧把时间字段统一用本地时间理解代码里取日期时用django.utils.timezone.now()显示时交给前端格式化避免两边各改各的造成时间错乱。3.2 ORM 查询与删除对象延迟执行带来的两个坑Django ORM 的 QuerySet 是延迟执行的赋值时不查库真正取值时才发 SQL。这个特性在写图书列表时很舒服但在「先查询再删除」的场景里会坑人很多人写Book.objects.filter(available_count0).delete()之前想先看一眼删除对象于是先books Book.objects.filter(available_count0)打印了books再调用books.delete()没问题。但如果在查询和删除之间对同一模型做了filter链式复用等于构造了两个不同 SQL后一个的条件没跟前一个保持一致删错数据就发生了。我踩过的具体例子是「删除所有下架图书」时先筛选了status0后面又在同一个变量上追加了.filter(publisher_id3)实际删除范围变成了两个条件的交集跟预想完全不一样。所以删除对象的铁律是删之前先确认 SQL 的 WHERE 条件是什么。最稳妥的方式是把主键列表 QuerySet 显式转成list()再基于这个列表去删# 正确做法先把要删除的对象主键固定下来 books_to_delete list(Book.objects.filter(available_count0).values_list(id, flatTrue)) Book.objects.filter(id__inbooks_to_delete).delete()values_list(id, flatTrue)只取主键列不加载整行数据内存占用小。flatTrue表示返回一维列表而不是元组列表。删除时还有一层外键约束要当心BorrowRecord 对 Book 的外键设了PROTECT所以有借阅记录的图书直接delete()会抛ProtectedError。这不一定是坏事但它意味着「物理删除」在图书馆系统里几乎不可用正确姿势是给 Book 加一个is_active字段做软删除借阅查询时过滤掉即可。图书借出排行的统计查询也是答辩时高频问题这类聚合 SQL 在 ORM 里用Count解决注意annotate返回的是新的 QuerySet不是我们惯用的数据字典from django.db.models import Count, F # 找出被借次数最多的前五本书 hot_books Book.objects.annotate( borrow_countCount(borrowrecord) ).order_by(-borrow_count)[:5] for book in hot_books: print(book.title, book.borrow_count)annotate的字段名borrow_count是动态加上的Count(borrowrecord)里的字符串是 BorrowRecord 里指向 Book 的外键字段名Django 会自动 JOIN 借阅表做分组。3.3 演示数据让答辩现场不空洞的准备工作很多毕设系统写完数据库里只有测试时随手点的三五条数据答辩时演示「图书检索」的效果非常难看。我一般会写一个独立的seed.py脚本放在项目根目录用 Faker 批量生成 200 本图书、50 个读者、800 条借阅记录生成逻辑里让一部分借阅记录已经归还一部分超期未还这样管理员界面每个列表都有内容可看。# seed.py import random from datetime import date, timedelta from django.contrib.auth.hashers import make_password from books.models import User, Book, BorrowRecord ADMIN_PASSWORD make_password(admin123) for i in range(50): User.objects.create_user( usernamefstudent{i:03d}, password123456, student_idf2024{i:04d}, rolestudent ) titles [计算机网络, 数据结构, 算法导论, 软件工程, 数据库系统概论] for i in range(200): Book.objects.create( titlef{random.choice(titles)} 第{i}版, isbnf978-7-{random.randint(100000, 999999)}-{random.randint(1000, 9999)}-{random.randint(0, 9)}, total_countrandom.randint(1, 5), available_countrandom.randint(0, 5) )运行这个脚本要借助python manage.py shell seed.py或python manage.py shell -c exec(open(seed.py).read())因为脚本里 import 了 Django 模型必须在 Django 环境里跑。生成借阅记录时注意due_date最好用date.today() timedelta(days30)这类相对日期而不要写死某一天否则答辩时日期已经过期界面上一片红色超期标记场面尴尬。真实感的另一个关键点图书的available_count不能总是等于total_count要有一些书显示「已借完」借还按钮才会出现置灰状态答辩老师一看就知道状态联动是真实做出来的。4. Vue 前端登录、路由守卫和接口联调4.1 用 Vite 起前端工程并配好代理前端部分我直接选 Vite 而不是 Vue CLI创建速度更快依赖也更干净。创建完成后第一件事不是写页面而是配开发代理。前后端分离项目里前端跑在 5173 端口后端跑在 8000 端口浏览器的同源策略会让所有axios请求直接失败。最省事的解法是在vite.config.js里配置server.proxy让前端把/api开头的请求转发到后端地址。// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } })changeOrigin: true会让转发请求的 Host 头变成后端地址Django 那边看到的就是来自本机的正常请求不会触发跨域拦截。虽然 Django 端已经配了django-cors-headers但开发环境走代理后 CORS 基本不会再触发所以这两个方案一个管开发、一个管部署都要保留。配好代理后顺手启动npm run dev验证一次再看下一步。4.2 axios 封装、路由守卫和权限菜单前端把 axios 实例单独抽出一个文件好处是 token 拦截器、错误提示只写一遍。请求拦截器统一从 localStorage 取 token 加到请求头响应拦截器统一处理 401 状态码登录过期直接跳回登录页。// src/utils/request.js import axios from axios import router from ../router const request axios.create({ baseURL: /api, timeout: 8000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { localStorage.removeItem(token) router.push({ name: login }) } return Promise.reject(error) } )baseURL: /api配合 Vite 代理前端代码里请求路径写成/books/就够了代理会自动拼成http://127.0.0.1:8000/api/books/。那 Django 端怎么让接口刚好挂在/api前缀下在urls.py里把路由注册包在path(api/, include(...))即可前后端约定一个统一前缀联调时路径冲突会少很多。路由守卫方面vue-router 的beforeEach里根据 token 判断是否放行管理页面再检查localStorage.getItem(role)是否等于 admin双条件都满足才让进。Vue 路由传参这里有一个容易懵的点从图书列表页跳详情页有人习惯用query传整本书的对象刷新页面后参数丢失页面就空了。正确做法是只传id详情页通过route.params.id重新调接口拿数据这才符合前后端分离的数据流转逻辑。4.3 联调时的三个关键排查点后端跟前端联调出现问题的概率最高的不是业务逻辑而是两边对「数据格式」的理解不一致。Django 的 DRF 默认返回的是 JSON 数组或对象时间字段格式是2024-12-01或完整的 ISO 字符串前端如果用new Date()直接解析时区差异会导致日期偏移一天。我一般在 serializer 里把所有日期字段统一格式化成YYYY-MM-DD前端拿到后当纯字符串展示不做 Date 转换。第二个高频问题是 404。前端请求路径写的是/api/books/后端路由注册的是router.register(books, BookViewSet)但router.register注册出来后列表接口路径默认带不带斜杠都能访问自定义 action 的路径却是/api/books/{id}/borrow/这种带尾部斜杠的格式。DRF 默认APPEND_SLASH开启所以前端请求/borrow少了斜杠时Django 会先 301 跳转带斜杠的地址POST 请求在跳转时可能丢方法变成 GET表现为「借书请求发出去前端收到 405」。排查时先看 Network 面板里有没有 301有的话直接给前端路径补上尾部斜杠别改后端配置。第三个是 token 失效时机。simplejwt 默认 access token 有效期五分钟足够短答辩演示时如果提前登录、讲了半天再操作第一次请求必然 401。要么把SIMPLE_JWT[ACCESS_TOKEN_LIFETIME]调长一点比如timedelta(hours2)要么前端在 401 时自动用 refresh token 刷新后再重放一次请求。毕业设计阶段我更推荐前者后者需要处理并发重放问题复杂度对答辩性价比不高。5. 前后端分离项目排查五个高频坑与修复办法5.1 环境与依赖类MySQL 连接报错和 Python 版本问题现象一执行python manage.py migrate报错django.core.exceptions.ImproperlyConfigured或者提示找不到 MySQLdb 模块。原因装了 pymysql 但没在项目__init__.py里执行install_as_MySQLdb()。这个错在 Windows 上尤其常见因为很多教程默认你装 mysqlclient而 mysqlclient 在 Windows 上需要编译环境装不上就跳过了。解决确认__init__.py里那两行代码存在并已保存如果还是报错检查settings.py的ENGINE是不是误写成了django.db.backends.mysql以外的值。现象二MySQL 8 连接时提示Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8 默认认证插件是caching_sha2_password而pymysql对它的支持在部分版本上有兼容问题。解决登录 MySQL 后执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;把认证插件改成mysql_native_password然后刷新权限。这是毕设阶段改动最小、见效最快的方案。5.2 数据与权限类CSRF 403 和删除数据翻车现象三所有 POST、PUT、DELETE 请求返回 403 Forbidden响应体里提到 CSRF 验证失败。原因Django 的CsrfViewMiddleware默认开启而后端接口是给无状态前端用的前端没有维护 CSRF token。前后端分离项目里继续用 SessionAuthentication 就会撞上这堵墙。解决REST_FRAMEWORK 里把默认认证类改成 JWT 认证CsrfViewMiddleware就不参与 JWT 请求的校验了。REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: ( rest_framework_simplejwt.authentication.JWTAuthentication, ), DEFAULT_PERMISSION_CLASSES: ( rest_framework.permissions.IsAuthenticated, ), }注意改完DEFAULT_AUTHENTICATION_CLASSES后之前用 Django Admin 登录调试接口的 session 方式就失效了测试接口要改用带 token 的方式Postman 里在Authorization标签页选择 Bearer Token 填进去。现象四删除图书时被ProtectedError拦下或者反过来级联删除把借阅记录全清了。原因外键on_delete没想清楚。PROTECT是保护有引用就禁止删除CASCADE是级联删主表连从表一起删。图书馆管理系统里借阅记录是流水数据不能跟着图书一起消失所以必须PROTECT。解决已经CASCADE的项目去 migration 文件里把on_deletemodels.CASCADE改成PROTECT执行makemigrations和migrate然后去 MySQL 里确认外键约束已经重建。5.3 联调与部署类接口通但页面报错、部署后 404现象五开发环境一切正常部署到服务器后用 Nginx 访问页面一调接口就 404。原因Vite 的 proxy 只在开发服务器里生效打包后的静态文件没有代理能力浏览器直接请求服务器同源地址的/api而 Nginx 没把/api转发给 Django。解决在 Nginx 配置里加上一层反向代理规则。location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /var/www/library_front/dist; try_files $uri $uri/ /index.html; }第一段把/api前缀的请求转给 8000 端口上的 Django 服务第二段让所有非 API 请求都回落到前端的 index.html这样 Vue Router 的 history 模式刷新页面也不会 404。部署时还有一层容易忽略Django 的ALLOWED_HOSTS必须加上服务器的 IP 或域名否则后端直接拒绝非本地 Host 的请求现象是 Nginx 显示 502 或 Django 返回 400 Bad Request。6. 答辩前一天的验证清单与演示顺序系统的工程部分全部跑通后最后半天不要写新功能按下面的顺序把整条链路走三遍。第一遍做功能验证第二遍专门触发异常分支第三遍按答辩讲稿的节奏计时走保证每操作一步都有话可说。顺序操作预期结果答辩要点1用管理员账号登录跳转到管理后台讲 JWT 登录流程与 token 存储位置2图书管理页新增一本书列表出现新书记录讲 DRF 序列化器与 MySQL 写入链路3创建一个读者账号读者列表出现新用户讲 AbstractUser 扩展与角色字段4用读者身份登录并借一本书图书库存减一借阅记录生成讲事务包裹借书逻辑的边界条件5还书操作库存加回记录写入归还日期讲 available_count 和 total_count 的差异6查看超期未还列表超期记录高亮或标记讲 due_date 与当前日期的比较查询7退出登录后直接访问后台页被路由守卫拦回登录页讲前端权限控制与 token 失效处理验证时要特意测试两个反例借一本库存为零的书页面应该置灰或报错用普通读者账号访问管理员删除接口后端应该返回 403。这两个场景在答辩提问环节出现频率极高提前验证能避免现场翻车。数据库备份用python manage.py dumpdata --natural-primary导出 JSON写在论文附录里答辩演示中途万一数据被改乱一句命令就能恢复。这套系统我前后带过三届学生做完最后悔的是早期没在项目开始就统一前后端接口前缀和日期格式导致联调阶段反复改代码。把这个约定写进项目 README 的第一行比任何架构图都管用。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网