ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django与MySQL的图书管理系统设计与实现解析

基于Django与MySQL的图书管理系统设计与实现解析 简介在Web开发中关系型数据库与ORM对象关系映射是构建业务系统的核心基础。Django作为Python主流Web框架其内置ORM能够将数据模型与MySQL表结构高效映射开发者无需手写SQL即可完成增删改查。通过合理设计数据库表关联如以冗余字段维护库存可借数量能够平衡查询性能与数据一致性。以图书借阅场景为例借书、还书涉及多表状态变更必须借助事务与行级锁保证并发安全。同时登录鉴权、分页搜索、管理后台等通用能力均可基于Django生态快速落地。本文从工程实践出发解析一套基于DjangoMySQL的图书管理系统涵盖模型设计、核心功能实现、环境配置与常见问题排查为学习Web开发和数据库实战提供完整参考。 做图书管理系统这套东西我带过的每个新人几乎都写过一遍。它表面看就是个标准的增删改查但真正从零落地的过程中你会碰到 ORM 模型怎么设计、MySQL 表怎么关联、借书还书的状态怎么流转、登录鉴权怎么接、分页搜索怎么做……这一圈走下来学到的东西远比“抄一遍代码”多得多。今天分享的版本是基于 Python Django MySQL 实现的图书管理系统源码和数据库文件都是完整的解压配置好环境就能直接跑。你可以拿它当课设作业也可以作为简历上的练手项目还可以在其中继续加功能。下面我把整个项目的设计思路、数据库模型、核心功能实现、部署运行过程以及我踩过的坑一次性讲清楚。1. 项目整体设计与技术选型1.1 为什么选 Django MySQL 这套组合图书管理系统的数据模型不算复杂但对数据库的关联查询、事务处理、并发一致性还是有一定要求的。选 Django 的原因很简单它内置 ORM你写 Python 类就能映射数据库表不需要手写大量 SQL自带 Admin 后台调试数据的时候非常方便MVT 架构对页面渲染和业务逻辑的分离也很清晰适合中小型 Web 系统。MySQL 则是当前使用最广的开源关系型数据库事务支持成熟InnoDB 引擎在处理借书、还书这种需要状态更新的场景下很稳。Django 官方虽然默认推荐 PostgreSQL但 MySQL 在国内的普及度更高部署环境更好找数据导入导出、备份恢复的工具链也齐全。用生活化的类比来说Django 像一个装修公司帮你把水电路由、地板模板、家具模型都安排得明明白白MySQL 则是你家的储物仓库所有东西最终都按规矩放进对应的货架上。两者配合起来开发效率和学习成本都很友好。1.2 功能模块与整体结构设计这套系统的核心模块我按角色和业务拆成了这么几块登录与权限管理员登录、退出普通页面受限访问。图书管理图书信息录入、编辑、删除、列表展示、关键字搜索、分页。读者管理读者信息维护区分不同读者类型与借阅权限。借阅管理图书借出、归还、续借自动记录借阅时间计算是否超期。数据统计简单统计馆藏总数、当前借出数量、超期未还数量。页面结构上我没有做前后端分离而是直接用 Django 的模板系统渲染 HTML。这样做的好处是避免引入 Vue/React 后还要处理跨域、接口鉴权、打包构建这些额外复杂度。对图书管理系统这种以表单和列表为主的项目来说服务端渲染反而是最省事、最容易出效果的方式。前端样式用了 Bootstrap 4CDN 引入即可不用额外下载静态包。整体页面保持了“左边导航栏 右侧内容区”的后台管理布局逻辑清晰用户一眼就知道自己在哪个模块。1.3 目录结构与源码包说明拿到源码包后你会看到下面这种结构book_manager/ ├── manage.py ├── books/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── forms.py │ ├── migrations/ │ └── templates/ │ └── books/ ├── readers/ │ ├── __init__.py │ ├── admin.py │ ├── models.py │ ├── views.py │ ├── urls.py │ └── templates/ ├── borrow/ │ ├── __init__.py │ ├── admin.py │ ├── models.py │ ├── views.py │ └── urls.py ├── templates/ │ ├── base.html │ └── login.html ├── static/ │ └── css/ │ └── custom.css ├── db.sqlite3 └── book.sql我用两个数据库方案都测试过项目默认使用db.sqlite3方便快速跑通同时提供了book.sql方便你在 MySQL 中重建完整数据。源码包里的README.md也会写明环境版本和启动步骤跟着操作即可。2. 数据库设计与核心模型2.1 数据表规划与字段说明图书管理系统最少需要四张核心表图书表、分类表、读者表、借阅记录表。如果涉及管理员账号还需要用户表。表结构设计得是否合理直接决定后续写业务逻辑时是轻松还是痛苦。我设计的字段如下表名主要字段说明bookid, title, author, isbn, publisher, publish_date, total_copies, available_copies, category_id, cover图书基础信息与库存categoryid, name图书分类readerid, reader_no, name, phone, email, max_borrow_count, created_at读者信息borrow_recordid, book_id, reader_id, borrow_date, due_date, return_date, status借阅记录与状态userid, username, password, is_superuserDjango 内置用户扩展这里要特别解释一下available_copies字段。它表示“当前可借出的副本数”。为什么要单独存一个冗余字段而不是每次通过借阅记录反查原因很简单每次借书时如果都要COUNT(*)统计未归还的借阅记录再拿总库存减去它性能虽然在小数据量下无所谓但逻辑上容易出错比如遇到历史脏数据、手工改库等情况时统计结果可能对不上。用冗余字段配合事务更新既直观又高效。2.2 Django ORM 模型定义解析在books/models.py里核心模型是这样的from django.db import models class Category(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name分类名称) class Meta: verbose_name 分类 verbose_name_plural verbose_name def __str__(self): return self.name class Book(models.Model): title models.CharField(max_length200, verbose_name书名) author models.CharField(max_length100, verbose_name作者) isbn models.CharField(max_length20, uniqueTrue, verbose_nameISBN) publisher models.CharField(max_length100, verbose_name出版社) publish_date models.DateField(verbose_name出版日期) total_copies models.PositiveIntegerField(default1, verbose_name总册数) available_copies models.PositiveIntegerField(default1, verbose_name可借册数) category models.ForeignKey( Category, on_deletemodels.SET_NULL, nullTrue, related_namebooks, verbose_name分类 ) cover models.ImageField(upload_tocovers/, nullTrue, blankTrue, verbose_name封面) created_at models.DateTimeField(auto_now_addTrue, verbose_name录入时间) class Meta: verbose_name 图书 verbose_name_plural verbose_name ordering [-created_at] def __str__(self): return self.title几个关键点uniqueTrue用在 ISBN 字段上避免同一本书重复录入。on_deletemodels.SET_NULL表示删除分类时该分类下的图书不受影响自动置空分类关联。related_namebooks让我可以从分类对象反向拿到该分类下所有图书比如category.books.all()。PositiveIntegerField用来限制库存数量不能为负数。借阅记录的模型放在borrow/models.py中class BorrowRecord(models.Model): STATUS_CHOICES [ (borrowed, 借出中), (returned, 已归还), (overdue, 已逾期), ] book models.ForeignKey(Book, on_deletemodels.CASCADE, related_nameborrow_records) reader models.ForeignKey(Reader, on_deletemodels.CASCADE, related_nameborrow_records) borrow_date models.DateField(auto_now_addTrue, verbose_name借出日期) due_date models.DateField(verbose_name应还日期) return_date models.DateField(nullTrue, blankTrue, verbose_name实际归还日期) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultborrowed) class Meta: verbose_name 借阅记录 verbose_name_plural verbose_name def __str__(self): return f{self.reader.name} - {self.book.title}2.3 借阅状态设计的常见误区很多初学者喜欢在 Book 表里放一个is_borrowed布尔值或者一个状态字段表示“在馆/借出”。这种设计在面对多副本图书时马上就会崩同一本书有 3 册你不可能用单个布尔值记录第几册被谁借走了。正确的做法是图书的库存状态用available_copies表示具体每次借阅行为用独立的borrow_record表记录每条记录对应“某个读者借了某一本书的一个副本”这个动作。而status字段我这里把“已逾期”也作为一种状态存储虽然它可以根据due_date和return_date动态算出但提前持久化更有助于列表筛选和统计展示。实际运行中我会用定时任务或查询时动态计算来同步这个状态。3. 核心功能实现与关键代码解析3.1 登录鉴权与访问控制Django 自带auth应用直接用就好不要自己写密码校验。登录视图我这样实现from django.contrib.auth import authenticate, login, logout from django.shortcuts import render, redirect def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(book_list) else: error 用户名或密码错误 return render(request, login.html, {error: error}) return render(request, login.html)所有业务视图都加上login_requiredfrom django.contrib.auth.decorators import login_required login_required def book_list(request): ...这样没登录的用户会被自动重定向到LOGIN_URL指定的登录页。权限控制做到这一步对课设和内部管理系统来说已经够了。3.2 图书列表、搜索与分页实现图书列表页是整个系统使用频率最高的页面。我一次把搜索和分页都做了。搜索用 Django 的Q对象可以同时在书名、作者、出版社三个字段上做模糊匹配。from django.core.paginator import Paginator from django.db.models import Q login_required def book_list(request): keyword request.GET.get(keyword, ) books Book.objects.select_related(category).all() if keyword: books books.filter( Q(title__icontainskeyword) | Q(author__icontainskeyword) | Q(publisher__icontainskeyword) ) paginator Paginator(books, 10) # 每页 10 条 page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, books/book_list.html, { page_obj: page_obj, keyword: keyword, })select_related(category)值得单独提一下。因为每本书都有分类列表页要显示分类名称。如果不用select_relatedDjango 会为每一本书额外执行一次分类查询这就是经典的 N1 查询问题。用select_related后一条 JOIN 就能把分类数据带出来列表页再也不用担心查询次数爆炸。前端模板中我通过page_obj.has_previous、page_obj.has_next控制上一页/下一页按钮同时在 URL 中带上keyword参数保证翻页后搜索关键字不会丢失。3.3 借书、还书流程与事务处理借书和还书是整个系统里最需要小心的地方因为涉及多个数据表的状态变更。借书逻辑检查读者是否存在且未超额。检查图书可借数量是否大于 0。创建借阅记录。图书available_copies减 1。这四个步骤不是孤立的中间任何一步失败都不能让数据库停留在一个“借阅记录已生成但库存没减”的中间状态。所以必须使用事务。from django.db import transaction from datetime import timedelta from django.utils import timezone login_required transaction.atomic def borrow_book(request, book_id): book Book.objects.select_for_update().get(idbook_id) reader request.user.reader # 检查读者借阅数量 borrowing_count BorrowRecord.objects.filter( readerreader, statusborrowed ).count() if borrowing_count reader.max_borrow_count: return JsonResponse({code: 1, msg: 已达最大借阅数量}) if book.available_copies 0: return JsonResponse({code: 1, msg: 该图书无可借副本}) due_date timezone.localdate() timedelta(days30) BorrowRecord.objects.create( bookbook, readerreader, due_datedue_date, statusborrowed ) book.available_copies - 1 book.save() return JsonResponse({code: 0, msg: 借书成功})这里我用了一个容易被忽视但很重要的方法select_for_update()。它会对选中的图书记录加行级锁防止两个用户同时看到可借数量为 1然后同时借出同一本导致库存变成负数。还书逻辑相对简单找到对应借阅记录把status改为returned填写return_date再把available_copies加回去即可。同样要放到事务里。3.4 Django Admin 后台的妙用Django Admin 是调试阶段效率最高的工具没有之一。在books/admin.py里注册模型from django.contrib import admin from .models import Book, Category admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display [title, author, publisher, available_copies, total_copies] search_fields [title, author, isbn] list_filter [category]启动服务后访问/admin/不需要写一行前端代码就已经有了图书列表、搜索、过滤、增删改查功能。我通常建议在开发初期用 Admin 录入一批假数据方便调试业务逻辑。等业务逻辑稳定了再回头看前端展示也不迟。4. 环境配置、数据库初始化和完整运行流程4.1 创建虚拟环境并安装依赖不管你是想在本地跑还是部署到云服务器我都建议先建虚拟环境避免污染系统 Python。python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install django4.2 pymysql版本说明Django 我用的是 4.2 LTS这也是官方维护期较长的版本适合学习和生产。pymysql是 Python 连接 MySQL 的驱动库用途在下面说明。4.2 MySQL 建库与 settings 配置先登录 MySQL 创建数据库注意字符集CREATE DATABASE book_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;在项目的settings.py中将DATABASES改为DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: book_manager, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, } }然后在settings.py同级目录的__init__.py中添加import pymysql pymysql.install_as_MySQLdb()这行的作用是把pymysql伪装成 MySQLdb让 Django 在调用django.db.backends.mysql时能找到驱动。这是国内 Python 开发者连接 MySQL 最常用的方案省去编译mysqlclient的麻烦。4.3 数据迁移与初始数据导入新建数据库后直接执行python manage.py makemigrations python manage.py migrate这里会为所有应用创建数据表。如果你用的是我提供好的完整数据库文件也可以直接把book.sql导入mysql -u root -p book_manager book.sql两种方式各有利弊migrate方式是 Django 官方推荐的标准流程表结构完全由 ORM 控制导入 SQL 文件则更快数据也齐全。我的建议是第一次学习时用migrate加createsuperuser体验一遍 Django 的迁移流程想快速看到完整效果的直接导入 SQL 文件。还要注意如果你在models.py里改动过字段比如新增了一个字段记得重新执行makemigrations和migrate。这是初学者最容易卡住的地方改完模型忘记迁移页面一直报字段不存在。4.4 启动服务与多环境注意事项本地启动python manage.py runserver 0.0.0.0:8000浏览器访问http://127.0.0.1:8000/默认管理员账号密码可以在README.md里查看。如果你要部署到 Linux 服务器需要额外注意几件事DEBUG False后静态文件要使用python manage.py collectstatic收集MySQL 要允许远程连接生产环境建议使用 Gunicorn Nginx 的部署方式不要把runserver当作正式服务。当然这是后话课设或本地演示阶段runserver完全够用。5. 常见问题与排查技巧实录5.1 数据库连接失败Driver not loaded这是我最常见到的问题报错大致长这样django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. Did you install mysqlclient?原因很简单你配置了 MySQL 数据库但没有安装驱动。前面我已经在前面放了解法在settings.py同级的__init__.py里写上import pymysql pymysql.install_as_MySQLdb()顺带确认pip list里是有pymysql的。如果你的环境里已经装了mysqlclient那就不需要再装pymysql两个驱动共存反而可能冲突。5.2 中文乱码问题如果导入book.sql后页面上书名、作者显示成???基本可以确定是数据库字符集问题。MySQL 建库时一定要带上DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci同时检查 Django 的settings.py中LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ TrueLANGUAGE_CODE zh-hans会让 Django Admin 后台显示中文TIME_ZONE Asia/Shanghai保证时间字段正确显示北京时间。如果USE_TZ True你在代码里使用timezone.localdate()而不是datetime.datetime.now()否则时间会相差 8 小时。5.3 借书时库存扣减异常我在测试过程中曾经遇到过一次available_copies为负数的情况。排查后发现是因为我在添加借阅记录时没有使用事务并发请求下两条记录同时读取了库存各自判断available_copies 0随后都执行了减一操作其中一个执行后库存变成了 -1。解决办法就是我前面写到的用transaction.atomic包裹整个借书逻辑并配合select_for_update()行级锁。这里多说一句select_for_update()只能在事务内使用如果你不加事务装饰器Django 会报TransactionManagementError。5.4 静态文件加载失败页面样式全丢了控制台一片 404。这种情况在DEBUG True时通常不会出现如果你把DEBUG改为False后静态文件就无法访问了。解决方法是在settings.py里配置好STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] STATIC_ROOT BASE_DIR / staticfiles然后执行python manage.py collectstatic对于课设演示来说直接把DEBUG保持为True是最省事的但如果你要写毕业设计文档或者部署建议把STATIC_ROOT和collectstatic的流程跑一遍。5.5 常见问题速查表现象原因解决方案报Error loading MySQLdb module没有安装 MySQL 驱动安装 pymysql 并调用install_as_MySQLdb()中文显示为???数据库字符集不对重新以utf8mb4建库并导入数据时间相差 8 小时TIME_ZONE或USE_TZ配置不对设置TIME_ZONE Asia/Shanghai统一使用timezone.localtime()库存变成负数借书逻辑没有加锁和事务用select_for_update()transaction.atomic修改模型后字段报错没有执行迁移执行makemigrations和migrate页面样式 404静态文件路径配置错误配置STATICFILES_DIRS并执行collectstatic登录后跳转不生效LOGIN_URL未配置在settings.py中设置LOGIN_URL /login/6. 项目扩展方向与个人实操体会做完基础功能之后我建议你不要急着把项目封装交付可以再往深走一步。这个系统很自然的扩展点包括增加 Excel 导入导出借助openpyxl或pandas把图书列表一键导入数据库做批量数据维护。增加超期提醒写一个 Django 管理命令每天扫描借阅记录把due_date早于当前日期的记录状态改为overdue并发邮件或站内信通知读者。增加统计图表用 ECharts 或 Chart.js 展示各类图书的借阅热度、每月借还趋势。这部分会让你的项目丰满不少面试时也更有话聊。增加预约功能当某本书可借库存为 0 时读者可以预约管理员归还后自动通知下一位排队读者。我自己在实际开发中的体会是图书管理系统虽然代码量不大但它把 Web 开发的通用骨架完整地串了一遍。数据库建模、ORM 操作、事务与并发、鉴权、模板渲染、分页搜索、部署排错这些技能在你做任何一个真实项目时都会用到。最后分享一个小技巧不要把所有逻辑都堆在views.py里。借书、还书、续借这类核心业务最好单独封装成 service 层的方法视图只负责接收请求和返回响应。这样代码结构更清晰以后加单元测试也好写。我在这个版本里已经把核心借还逻辑独立拆开你运行源码时可以重点看一下这一层的组织方式。本文还有配套的精品资源点击获取
返回列表