简介基于Django与MySQL的图书管理系统源码包面向需要课程设计、毕业设计或实际项目参考的Web开发者提供了从数据库设计到前端交互的完整实现方案。系统涵盖图书增删改查、批量入库、多条件排序、借阅续借及归还、用户注册登录等核心功能结合Django自带的认证机制确保操作安全能帮助读者深入理解MVT架构、ORM映射、模板渲染等关键技术。压缩包共81个文件既包含22个Python源码文件用于业务逻辑与配置也有16个HTML模板负责页面展示4个JavaScript与4个CSS文件优化交互样式另附项目说明docx与README等文档整体大小2.71MB目录层次清晰方便按功能模块专项阅读。目前已有187人学习下载参考者可获得可直接运行的系统源码、数据库交互示例、部署配置详解并可作为二次开发的基础项目使用。1. 为什么图书管理系统是DjangoMySQL最合适的入门战场图书管理系统的业务边界足够清晰却又覆盖了一整套 Web 项目必须面对的完整链路数据建模、ORM 操作、用户交互、借阅状态流转、权限控制、部署上线。用 Django 做后端MySQL 做持久化正好把“框架怎么管数据”和“数据库怎么存数据”之间的接缝暴露得明明白白。新手可以通过它理解 MVT 和关系型数据库的配合方式老手则能在借阅并发、索引优化、表单校验这些点上重新审视自己的习惯。这个标题看起来像课程作业实际是检验一个人能否把 Django 的 ORM 和 MySQL 的表结构设计串起来的最低成本载体。你不需要理解分布式不需要微服务只需要一台能跑 Python 和 MySQL 的机器。2. 设计与建模把图书管理系统拆成 Django 模型和 MySQL 表2.1 数据模型设计图书、读者、借阅记录的关系一个图书管理系统的核心实体是图书、读者和借阅记录。绝大多数“基于 Web 的图书管理系统”都逃不开这三张表差别只在于读者是否复用 Django 自带的 User 模型。常见的做法是新建一个 Profile 关联 User或者直接自己建 Reader 表便于记录学号/工号和借阅额度。我一般会这样设计模型关键字段说明Booktitle, author, isbn, total_count, available_countavailable_count 是可用副本数而不是查询时动态计算避免借出后不好统计Readeruser(OneToOne), student_id, max_borrow关联 Django 用户同时保存业务编号BorrowRecordbook, reader, borrow_date, due_date, return_datereturn_date 为 NULL 表示未归还为什么要把 available_count 冗余在 Book 表里因为在借阅事务里你需要一个原子操作来扣减存量。如果每次都 COUNT(BorrowRecord)在高并发下很容易出现超借。冗余字段配合 SELECT FOR UPDATE 或者F()表达式能在单库场景下把冲突概率降到最低。2.1.1 模型代码示例from django.db import models from django.contrib.auth.models import User class Book(models.Model): title models.CharField(max_length200, db_indexTrue) author models.CharField(max_length100) isbn models.CharField(max_length20, uniqueTrue, nullTrue) total_count models.PositiveIntegerField(default1) available_count models.PositiveIntegerField(default1) class Meta: ordering [title] class Reader(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) student_id models.CharField(max_length20, uniqueTrue) max_borrow models.PositiveIntegerField(default5) class BorrowRecord(models.Model): book models.ForeignKey(Book, on_deletemodels.PROTECT) reader models.ForeignKey(Reader, on_deletemodels.CASCADE) borrow_date models.DateField(auto_now_addTrue) due_date models.DateField() return_date models.DateField(nullTrue, blankTrue) def is_returned(self): return self.return_date is not None逻辑说明Book.available_count不是派生字段而是业务冗余必须在借书和还书事务里手动维护。BorrowRecord.book使用PROTECT防止借阅记录存在时删除图书导致历史数据悬空。one_to_one字段在点击回收站图标把整个用户删除时会把关联 Reader 一起删掉这是常见预期但如果你要保留读者历史就需要改成外键并加related_name。参数说明db_indexTrue告诉 MySQL 为 title 创建普通索引uniqueTrue会为 isbn 创建唯一索引。注意 unique 同时隐含db_indexTrue不要重复写。max_length必须显式指定否则 MySQL 会报错因为 Django 对 CharField 默认长度是 255但显式写出来能提醒你业务意义。2.2 Django 模型到 MySQL 的迁移过程模型定义后真正让 MySQL 出现表的是 Django 的迁移系统。这个过程有三步makemigrations、migrate和sqlmigrate。其中sqlmigrate不执行 SQL而是打印将要执行的语句很适合在敏感环境里确认操作。python manage.py makemigrations library python manage.py sqlmigrate library 0001 python manage.py migrate第一行根据library应用下的 models.py 生成迁移文件不连数据库因此语法错误会在这一层暴露。第二行把 0001 迁移转成 MySQL 的 DDL你可以检查索引名、外键名和字符集是否带utf8mb4。第三行真正执行。如果迁移卡住先看 MySQL 是否有长事务锁住了django_migrations表而不是盲目删表。另外如果你的项目之前用的 SQLite后来换成 MySQL常见做法是清空所有迁移记录、保留模型定义然后重新生成初始迁移。千万不要以为直接改 DATABASES 就完事因为内置的 auth 迁移和你的业务迁移之间有依赖顺序依赖表如果已经存在migrate 会跳过造成不一致。2.3 模型里容易被忽略的参数on_delete、db_index、unique这几个参数决定数据完整性也直接影响 MySQL 的行为。很多人只记得on_deletemodels.CASCADE但图书管理系统里删除一个读者不应该把他的借阅历史清空。PROTECT会阻止删除SET_NULL需要外键字段设为nullTrue。下面是三种常见场景删除图书时借阅记录应该被阻止用PROTECT删除读者时保留历史借阅但不再关联用户把外键改成SET_NULL并允许空删除用户时读者档案和借阅记录全部清理用CASCADEdb_index要加在where和join频繁出现的字段上但不要给所有字段都加因为 MySQL 的联合索引可以覆盖多个查询路径。比如借阅记录经常按book_id和return_date查询那么应该在模型里声明Meta.indexes而不是分别加两个单列索引。class Meta: indexes [ models.Index(fields[book, return_date], nameidx_book_return), ]这样生成的 MySQL 索引名为idx_book_return覆盖“查某本书所有未还记录”和“查某本书所有历史记录”两类查询。注意顺序等值查询字段放前面范围查询字段放后面这是 MySQL 最基础的原则。3. 环境搭建与数据库连接让 Django 和 MySQL 实际跑通3.1 mysqlclient 安装的常见坑与 Python 版本对应Django 连接 MySQL 最常见的驱动是mysqlclient它比pymysql的性能更接近 C 客户端且支持较新的 MySQL 8.0 认证插件。安装时最常见的坑是缺少系统依赖导致pip install mysqlclient编译失败。在 Debian/Ubuntu 上需要先装libmysqlclient-dev在 CentOS 上是mysql-develWindows 则建议直接下载预编译的 wheel 文件。sudo apt install default-libmysqlclient-dev -y pip install mysqlclient如果你的项目用的是 Python 3.12 以上官方 wheel 可能还没覆盖当前版本这时可以退一步用pymysql但需要在__init__.py里加上pymysql.install_as_MySQLdb()。我一般推荐团队统一 Python 3.10 或 3.11这部分 Python 版本相关的问题会少很多。mysqlclient安装成功后import MySQLdb不会报错。如果仍然报ModuleNotFoundError检查当前终端激活的是不是同一个虚拟环境。很多人在全局环境和 venv 之间反复切换导致驱装置装到了另一个环境里。3.2 settings.py 中 DATABASES 配置参数详解Django 的 DATABASES 配置不止 host、port、user、password。实际生产环境还需要考虑CONN_MAX_AGE、OPTIONS和TIME_ZONE。下面是一份可直接抄的配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: library, USER: library_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, connect_timeout: 5, }, } }说明CONN_MAX_AGE表示连接复用 60 秒避免每次请求都建立新 TCP 连接这对 MySQL 的max_connections压力改善明显。charset必须设成utf8mb4否则 emoji 和生僻字会存不进去。init_command里的 sql_mode 会让插入超长字符串时直接报错而不是被 MySQL 静默截断开发阶段能帮你尽早发现问题。connect_timeout很短是为了在 MySQL 宕机时让 Django 请求快速失败而不是占用进程等待默认的 30 秒。如果你用了连接池中间件这里的CONN_MAX_AGE一般设成 0因为连接池自己管理生命周期。3.3 用 Django 原生 ORM 执行图书查询与删除对象连接数据库后最应该掌握的是 ORM 的惰性查询和删除行为。图书管理系统里“删除对象”有几种不同写法效果差别很大。直接objects.all().delete()会返回删除条数而objects.filter(...).delete()只删除筛选结果。# 查询所有可用图书 available_books Book.objects.filter(available_count__gt0) # 精确查询 ISBN没有则抛出异常 book Book.objects.get(isbn9787111213826) # 删除单个对象 book.delete() # 批量删除可用数量为 0 的图书注意会触发数据库级 DELETE deleted_count Book.objects.filter(available_count0).delete()这里最容易踩的坑是delete()返回的是一个元组第一个值是删除总数第二个值是每个模型删除数量的字典。如果你在代码里只取deleted_count Book.objects.filter(...).delete()拿到的是一个(total, dict)结构而不是整数。正确写法是total, details Book.objects.filter(...).delete()。删除图书时外键字段如果是默认的CASCADE关联的 BorrowRecord 会被连带删除这通常是业务事故。在 2.1 里我们把 BorrowRecord.book 设为PROTECT所以删除动作会抛出ProtectedError。你需要捕获这个异常提示“该书有借阅记录无法删除”。4. 图书管理系统的核心功能实现增删改查与借阅闭环4.1 基于类的视图和表单实现图书录入与编辑图书管理系统的增删改查用FormView或CreateView都可以。我更习惯用CreateView和UpdateView组合配合 Django 的 ModelForm能把字段校验和数据库写入绑成一条链。下面是一个典型实现# forms.py from django import forms from .models import Book class BookForm(forms.ModelForm): class Meta: model Book fields [title, author, isbn, total_count, available_count] widgets { isbn: forms.TextInput(attrs{placeholder: 可选}) } def clean_isbn(self): isbn self.cleaned_data.get(isbn) if isbn and Book.objects.filter(isbnisbn).exclude(pkself.instance.pk).exists(): raise forms.ValidationError(该 ISBN 已经存在) return isbn# views.py from django.urls import reverse_lazy from django.views.generic import CreateView, UpdateView from .models import Book from .forms import BookForm class BookCreateView(CreateView): model Book form_class BookForm template_name library/book_form.html success_url reverse_lazy(book_list) class BookUpdateView(UpdateView): model Book form_class BookForm template_name library/book_form.html success_url reverse_lazy(book_list)逻辑说明BookForm里重写了clean_isbn在exclude(pkself.instance.pk)的前提下做唯一性校验这样编辑时不会因为 ISBN 没有变化而报错。CreateView和UpdateView共用同一个模板Django 会自动传入form和object编辑时object有值创建时为空这不影响表单渲染。参数说明reverse_lazy是给类视图使用的因为类属性在模块导入时就会计算使用reverse会导致 URLConf 尚未加载而报错。fields里没有available_count因为它是业务状态不应该由手动录入。管理员可以在 admin 后台调整但普通表单不能碰。4.2 借阅/归还功能的事务处理借书动作包含两个操作在 BorrowRecord 中插入一条记录并把 Book.available_count 减 1。这两个操作必须在一个数据库事务里执行否则如果插入成功但扣减失败数据就会不一致。Django 的transaction.atomic()是解决这个问题的最轻量方案。from django.db import transaction from django.utils import timezone from datetime import timedelta from .models import Book, BorrowRecord, Reader def borrow_book(request, book_id): reader Reader.objects.select_for_update().get(userrequest.user) if reader.max_borrow reader.borrowrecord_set.filter(return_date__isnullTrue).count(): return JsonResponse({error: 超过可借数量}, status400) with transaction.atomic(): book Book.objects.select_for_update().get(pkbook_id) if book.available_count 0: return JsonResponse({error: 库存不足}, status400) book.available_count book.available_count - 1 book.save(update_fields[available_count]) BorrowRecord.objects.create( bookbook, readerreader, due_datetimezone.now().date() timedelta(days30) ) return JsonResponse({status: ok})说明select_for_update()会对 MySQL 的行级锁两个并发请求同时借同一本书时后一个会阻塞到前一个事务提交然后重新读取到新的available_count从而避免超借。save(update_fields[...])只更新该字段在 MySQL 里生成的 SQL 只有available_count ...减少锁范围。注意transaction.atomic()里的return并不是直接提交而是会先退出atomic代码块此时如果前面的操作都没有异常才会提交。上面的代码把库存检查和插入放在同一个事务里但return JsonResponse本身不影响事务状态。如果你在事务里捕获了异常但不重新抛出Django 会强制回滚这点要看懂。归还功能更简单把对应 BorrowRecord 的return_date设为今天同时把 Book.atomic_count 1。归还不要重新启用select_for_update因为可能跨很久但为了安全最好仍然在事务里做并检查return_date是否为空防止重复归还。4.3 Django admin 界面美化与列表定制admin 是图书管理系统展示给管理员的核心操作界面。默认情况下表格很简陋只显示__str__的结果搜索和筛选都没有。在 admin.py 里做以下配置能立刻把可用性提升一个量级from django.contrib import admin from .models import Book, Reader, BorrowRecord admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display [id, title, author, isbn, total_count, available_count] search_fields [title, author, isbn] list_filter [author] list_editable [available_count] ordering [-id] readonly_fields [id] admin.register(BorrowRecord) class BorrowRecordAdmin(admin.ModelAdmin): list_display [id, book, reader, borrow_date, due_date, return_date] list_filter [return_date] date_hierarchy borrow_datelist_display 里使用模型字段直接展示如果有is_returned这样的方法也可以加进去但注意方法不能接受参数。list_editable 允许在列表页直接修改 available_count省去进入详情页的点击。date_hierarchy 会在列表页生成按月份快速导航的日历条对借阅记录这种带日期字段的表很有用。如果你希望 admin 导航栏皮肤更好看可以使用 django-admin-interface 库但注意它需要放在 Django 的 INSTALLED_APPS 首位。对图书管理系统来说默认 admin 的定制已经完全够用过度美化反而耽误时间。5. 部署与优化宝塔部署 Django 项目和 MySQL 索引调优5.1 宝塔面板部署 Django 项目的完整步骤宝塔面板是目前在 VPS 上部署 Django 项目最常用的图形化方式尤其适合小型团队。整个流程可以归纳为创建站点、装 Python 环境、配 gunicorn、绑定 MySQL 库。下面是一套我在 Ubuntu 22.04 上常用的操作组合。# 1. 在宝塔文件管理里上传代码并创建虚拟环境 cd /www/wwwroot/library python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # 2. 收集静态文件 python manage.py collectstatic --noinput # 3. 用 gunicorn 启动 gunicorn library.wsgi:application \ --bind 127.0.0.1:8001 \ --workers 3 \ --threads 2 \ --timeout 60逻辑说明绑定地址是127.0.0.1因为面板的 Nginx 会监听公网端口并把请求转发到8001这个内部端口。workers 数量通常按 CPU 核心数 * 2 1 来定但这只是一个经验值。timeout 60表示超过 60 秒未完成的请求会被重试对借书事务来说不应该超时但如果 MySQL 有锁等待这个值可以放宽到 120。然后到宝塔面板的“网站”里新建一个反向代理站点域名或 IP 端口 80目标 URL 为http://127.0.0.1:8001并开启proxy_set_header Host $host。这一步直接决定 Django 的ALLOWED_HOSTS是否需要包含你的域名。如果忘了配置Django 会返回Bad Request (400)。根目录下的静态文件入口不要指向 Django 的/static/而是到宝塔面板文件中创建软链指向collectstatic的输出目录。生产效率最高的是直接用面板自带的“自定义配置”粘贴 Nginx 伪静态规则把静态请求直接交给 Nginx把其他请求交给 gunicorn。5.2 MySQL 连接池与查询优化参数MySQL 默认的单连接开销不小Django 的CONN_MAX_AGE只能复用同一进程内连接。当使用 gunicorn 多进程时每个 worker 都维护自己的连接池。为了减少 3306 端口握手次数可以在 MySQL 端调整部分参数。参数推荐值说明max_connections150~300按 worker 数和并发数估算wait_timeout60空闲连接秒数避免占用innodb_buffer_pool_size内存的 60%缓存表数据与索引max_allowed_packet64M防止大字段写入失败log_bin_trust_function_creators1开启 binlog 时避免导入存储过程报错在 Django 应用层借用django-db-connection-pool是很多生产项目的选择但它需要在 wsgi 启动时替换数据库后端。对于图书管理系统这种并发量不高的场景我更建议先调CONN_MAX_AGE和 MySQL 的max_connections不要过早引入连接池中间件因为中间件的连接回收和事务交互会引入额外复杂度。索引优化方面除了前面设置的idx_book_return借阅记录经常按due_date筛选“即将到期未还”的图书所以再给due_date加单列索引用处很大。在迁移文件里新增operations [ migrations.RunSQL( ALTER TABLE library_borrowrecord ADD INDEX idx_due_date (due_date), reverse_sqlALTER TABLE library_borrowrecord DROP INDEX idx_due_date ) ]RunSQL的好处是手动控制索引名避免 Django 自动名后缀改变。注意在borrow_date上已经有date_hierarchy使用的索引不要重复加。5.3 验证系统的可用性与备份策略部署完成后需要验证两件事首页是否能打开、借书功能在 MySQL 重启后是否还健壮。Django 自带的check命令可以快速检查数据库连接和应用配置python manage.py check --deploy mysql -ulibrary_user -p -e SHOW TABLES; librarycheck --deploy会提示比如 DEBUG 是否开启、静态文件是否配置正确。如果它报安全问题但不影响业务可以逐条核对。第二条命令直接确认表是否存在。备份策略上图书管理系统的数据总量不会很大用 mysqldump 每天全量备份即可。备份脚本放在宝塔计划任务中每天凌晨 3 点执行并保留最近 7 天的文件。mysqldump -ulibrary_user -ppassword --single-transaction --routines library | gzip /backup/library_$(date \%Y\%m\%d).sql.gz--single-transaction可以在不锁表的情况下备份 InnoDB 数据避免备份期间借书请求被阻塞。恢复时先建库再用 gunzip 管道导入注意不要使用 root 账号单独建一个带SELECT, LOCK TABLES权限的备份账号会让权限更清楚。验证备份可用性的最佳技巧是固定每周在临时库里恢复一次并用 Django 的showmigrations确认数据可以正常迁移。本文还有配套的精品资源点击获取