
简介这是一套面向计算机及相关专业毕业设计的图书馆管理系统完整项目基于PythonDjangoVueMySQL开发采用B/S架构覆盖管理员端、用户端与前台展示页面。功能包含图书分类、图书信息、借阅归还、罚金缴纳、留言板、个人中心、我的收藏与系统管理等模块适合需要毕业设计源码、论文和答辩支撑材料的本科生作为参考与二次开发基础。资源以zip压缩包形式交付整体约65.38MB上游未提供文件总数和类型清单暂不展开。包内通常整合源码、数据库脚本、毕业论文、答辩PPT及演示视频可据此快速搭建环境并理解图书借阅全流程节省从零搭建和撰写文档的时间。已有320人参与学习。整体内容偏重实践落地既有前后端代码与数据表结构也有论文框架和操作演示能帮助读者梳理系统设计思路完善毕业设计文档与答辩准备。1. 如果图书馆管理系统毕业设计让你卡在联调这一步先把项目跑起来再说图书馆管理系统基于PythonDjangoVueMySql是一套非常标准的Web全栈毕业设计项目后端用Django做业务逻辑和API前端用Vue 3做页面交互MySQL 8存数据。整套资源本身自带源码、数据库脚本、毕业论文、答辩PPT和视频演示大概覆盖了从开题到答辩的完整链路。这套东西最适合两类人一是课程设计时间紧、需要在两周内交付能演示的完整系统二是打算二次开发但不想从零写增删改查的Django入门者。我拆完这套代码之后最大的感受是——技术栈不炫技但每一层都踩在毕业设计的评分点上能演示借书、还书、逾期统计、读者管理、图书检索这套完整闭环。别急着改代码先把MySQL跑起来、把数据库脚本导进去、用Django自带的开发服务器把后端启动再用npm把Vue开发环境拉起来整套系统能在半小时内跑通。2. 整体架构与模块划分为什么这套系统选Django Vue而不是其他组合2.1 技术栈选型Django Vue 各自的边界在哪里毕业设计选技术栈最怕的不是用错而是用了自己讲不清楚的东西。这套系统选Django做后端核心原因是Django自带Admin后台、ORM和认证系统这三个能力刚好对应图书馆管理系统的三个硬需求图书和读者的增删改查、数据库表之间的关联查询、管理员和普通读者的权限区分。如果用FlaskAdmin后台要自己写如果用Node.js的ExpressORM要另外选型。而Django把这块全部内置了写代码的工作量至少省掉三分之一。Vue这边负责的是页面交互。图书馆管理系统虽然有后端渲染的能力但借书还书这类操作需要即时反馈比如输入读者编号后立刻显示读者信息、选择图书后实时显示馆藏数量这类体验用Vue的响应式数据绑定做起来最顺手。拿这套系统的前端来说图书检索页面用到了一个实时过滤功能读者在搜索框输入书名关键字列表立刻过滤匹配结果这个交互如果用Django的模板渲染来做每次输入都要刷新页面答辩时演示效果会差不少。前端和后端的通信方式是HTTP JSON这也是最常见的做法。后端把业务逻辑封装成符合RESTful风格的API接口前端的axios负责发起请求并把数据填充到页面。整个项目虽然分了三个部分Django后端、Vue前端、MySQL数据库但实际跑起来是两条线一条是浏览器请求Vue开发服务器另一条是Vue发请求到DjangoDjango再查MySQL。2.2 数据库表设计馆藏、读者、借阅记录是核心三张表图书馆管理系统的数据库设计核心表就三张图书表books、读者表readers、借阅记录表borrow_records。其他的表都是围绕这三张做辅助。我先说图书表字段设计上要区分“书目信息”和“馆藏信息”。很多初写毕业设计的人会把这两件事混在一张表里导致的情况是书架上有5本《Python编程》数据库里却只有一条记录还书的时候不知道还的是哪一本。这套系统的做法是book表存统一的图书信息用isbn做唯一标识另外再单独维护库存数量借书时库存减一还书时库存加一。借阅记录表是整个系统的核心也是答辩时评委最可能追问的地方。这张表至少要有这么几个字段借书时间borrow_time、应还时间due_time、实际归还时间return_time、借阅状态status。状态字段建议用整数表示而不是字符串0表示借出、1表示已还、2表示逾期未还这样在做统计的时候写SQL条件非常直接不需要字符串模糊匹配。下面这张表是数据库脚本里主要表的字段清单实际实现的MySQL脚本里都能找到对应定义。表名主要字段说明bookid, isbn, title, author, publisher, category, total_stock, current_stock, location图书书目信息isbn唯一readerid, reader_no, name, gender, phone, email, max_borrow, status读者信息reader_no用于借书识别borrow_recordid, book_id, reader_id, borrow_time, due_time, return_time, status, renew_count借阅记录status控制流程状态book_categoryid, category_name, sort_order图书分类与book表category字段关联userid, username, password_hash, role, name登录用户信息区分管理员与读者数据库脚本导入之后包里自带了一些演示数据大概包括几十本图书和十几个读者账号这个数据量对答辩演示来说足够了——正好能展示分页和检索功能又不会因为数据太多导致页面加载慢。如果用这套系统做二次开发最好保留这些初始数据不要清空重来省得后面演示时还要手工往数据库里塞数据。3. Django后端借书、还书、逾期判断的业务逻辑实现3.1 项目目录结构与App划分单App还是多AppDjango项目的目录结构直接决定了后续维护的时候想不想砸电脑。这套系统的后端拆成了两个Appbooks负责图书和借阅管理users负责读者、登录、权限。拆两个App而不是把所有模型丢在一个App里好处有两点第一models.py、views.py文件不会膨胀到几千行第二答辩的时候可以单独讲“我是按业务模块划分的books处理图书业务users处理用户业务”这种组织方式本身就是一个加分点。这种时候最忌讳把所有代码堆在一个app里一个views.py写了2000行看似工作量很大实际上评审老师随便翻一下就觉得没有工程组织能力。目录结构大致是这样books/models.py放图书和借阅记录的模型books/views.py放借还书的视图函数books/serializers.py放API序列化器users那边管理读者和登录。3.2 借书接口实现事务回滚是防止库存负数的关键借书这个功能是整个系统的命脉代码实现上要保证两件事同时成功把借阅记录插入borrow_records表把books表的库存current_stock减一。如果第一条执行成功、第二条失败就会出现“数据库里有一条借阅记录但书架上还有这本书”的脏数据。下面是借书视图的代码核心逻辑用的是Django的transaction.atomic装饰器来保证事务一致性from django.db import transaction from rest_framework.decorators import api_view from rest_framework.response import Response from books.models import Book, BorrowRecord from books.serializers import BorrowSerializer from django.utils import timezone from datetime import timedelta api_view([POST]) transaction.atomic def borrow_book(request): # 前端传参格式: {book_id: 1, reader_no: RD2024001} book_id request.data.get(book_id) reader_no request.data.get(reader_no) # step1: 检查读者是否存在且状态正常 reader Reader.objects.filter(reader_noreader_no, status1).first() if not reader: return Response({code: 400, msg: 读者不存在或已被禁用}) # step2: 检查图书库存是否大于0 book Book.objects.select_for_update().get(idbook_id) if book.current_stock 0: return Response({code: 400, msg: 库存不足}) # step3: 扣减库存创建借阅记录 book.current_stock - 1 book.save() # 借期默认30天计算应还时间 due_time timezone.now() timedelta(days30) BorrowRecord.objects.create( bookbook, readerreader, borrow_timetimezone.now(), due_timedue_time, status0, # 0表示借出 ) return Response({code: 200, msg: 借书成功, due_time: due_time.strftime(%Y-%m-%d %H:%M:%S)})这里有两个参数细节要说明。第一个是select_for_update()这个方法会对book那条记录加行级锁防止了两个并发请求同时读到库存为1然后都通过校验结果把库存扣成负数。毕业设计虽然不需要应付高并发但答辩时被问到“多人同时借同一本书怎么办”时这行代码就是有力的回答。第二个是timedelta(days30)直接算应还时间而不是把到期时间硬编码这样以后如果要改成14天借期只改这里一处就够了。3.3 还书与逾期判断状态字段的演进规则还书的逻辑比借书多了一个分支判断——这本书是按时归还还是逾期归还。逾期判断不能靠管理员肉眼对比日期必须由后端自动计算。代码层面是这样做的api_view([POST]) def return_book(request): record_id request.data.get(record_id) record BorrowRecord.objects.select_related(book, reader).get(idrecord_id) # 先判断当前时间是否晚于应还时间 now timezone.now() if now record.due_time: record.status 2 # 逾期归还 else: record.status 1 # 正常归还 record.return_time now record.save() # 归还后库存加一 book record.book book.current_stock 1 book.save() # 逾期天数用于前端显示 overdue_days (now - record.due_time).days if record.status 2 else 0 return Response({code: 200, overdue_days: overdue_days})状态字段在这里的设计值得说一句0是借出1是已还2是逾期。很多人会想用is_overdue这种布尔字段来表示是否逾期但这样遇到一个场景就露馅了——一本书逾期归还之后布尔字段倒底是False还是True作为历史记录它应该保留“曾经逾期”这个事实。用status整数状态机借阅记录从借出到归还的过程中状态只会单向变化不会产生歧义。逾期天数这里可以看到因为使用了(now - record.due_time).days而日期对象相减会返回一个timedelta对象取.days就是整数天数。实际开发时要注意这里如果时区没配置正确得出的天数可能会相差1这是我后面第5章联调避坑里要展开详细说的问题。3.4 API路由与视图集ModelViewSet替你做一半的CRUD图书管理、读者管理这两块的增删改查接口代码量其实很少。原因是用到了Django REST Framework自带的ModelViewSet。这套系统里图书的视图集就是这么写的from rest_framework.viewsets import ModelViewSet from books.models import Book from books.serializers import BookSerializer class BookViewSet(ModelViewSet): queryset Book.objects.all().order_by(-id) serializer_class BookSerializer pagination_class None # 默认不分页分页逻辑在search接口中实现只要这个类图书的新增、修改、删除、列表查询就全齐了。路由注册也只需要两行from rest_framework.routers import DefaultRouter router DefaultRouter() router.register(rbooks, BookViewSet) router.register(rreaders, ReaderViewSet) urlpatterns router.urls默认情况下ModelViewSet会生成这些接口POST到/api/books/新增GET到/api/books/列表GET到/api/books/{id}/详情PUT和DELETE到/api/books/{id}/更新和删除。这套RESTful风格的接口设计答辩时是能直接讲的我是用REST Framework的ModelViewSet统一封装了资源的增删改查所以接口风格一致、开发效率快。图书检索这里是可以稍微展示点技术细节的地方。图书馆系统的搜索需求不只是书名模糊查询还要支持“按类检索”和“按作者检索”用DRF自带的filter_backends实现起来很干净from rest_framework import filters from django_filters.rest_framework import DjangoFilterBackend class BookViewSet(ModelViewSet): queryset Book.objects.all().order_by(-id) serializer_class BookSerializer filter_backends [DjangoFilterBackend, filters.SearchFilter] filterset_fields [category, status] search_fields [title, author, isbn, publisher]前端只要请求GET /api/books/?searchpythoncategory1后端就会自动执行模糊搜索并叠加分类过滤不需要自己写SQL拼字符串。filterset_fields和search_fields都在后端定义了可过滤字段清单前端传超出范围的参数也不会生效这块在答辩演示时可以现场改URL给评委看效果变化。4. Vue前端从路由配置到Axios联调4.1 前端工程结构与Vue Router路由设计后端接口准备就绪后前端的工作量集中在页面和联调。这套系统的Vue部分用的是Vue 3 Vite目录结构是典型的Vite脚手架布局src/views存放页面组件src/router存放路由配置src/api统一封装请求方法。重点看路由的设计思路——路由表分两层是有意安排的。const routes [ { path: /, component: () import(../layouts/MainLayout.vue), children: [ { path: book, component: () import(../views/BookList.vue), name: 图书列表 }, { path: borrow, component: () import(../views/BorrowReturn.vue), name: 借还管理 }, { path: reader, component: () import(../views/ReaderList.vue), name: 读者管理 }, { path: stats/overdue, component: () import(../views/OverdueStats.vue), name: 逾期统计 } ] }, { path: /login, component: () import(../views/Login.vue) } ]外层套一个MainLayout.vue的好处是左侧导航栏和顶部状态栏只需要写一次所有子页面只负责内容区域。这种布局模式在Vue后台管理项目里很常见答辩时如果被问到“重复的导航栏是怎么处理的”直接说这是嵌套路由的父级组件效果页面响应时子路由出口会自动切换内容。动态导入需要说明一下作用() import(...)会让指定的页面组件在浏览器真正路由到该路径时才加载。好处是首屏只加载登录页和主框架的代码图书列表页的几百行Vue代码不会被一次性下载对开发环境的影响不大但对构建之后的生产包体积有明显优化。这套前端路由的设计还有一层业务考虑——把“图书列表”和“借还管理”拆成两个独立路由因为这两个页面的交互模式完全不同图书列表是纯查询展示借还管理是输入信息和提交。如果硬塞进一个页面事件处理的逻辑会牵扯不清。4.2 Axios请求封装与拦截器统一的BaseURL与Token鉴权前端和后端联调时最大的问题不是接口报错而是几十个组件里每写一次请求就复制粘贴一份完整的axios配置。这套系统的src/api/request.js中做了统一封装拦截器同时处理了请求头和错误状态。import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 8000 }) // 请求拦截器自动带上token request.interceptors.request.use(config { const token localStorage.getItem(library_admin_token) if (token) { config.headers.Authorization JWT ${token} } return config }) // 响应拦截器统一处理业务错误码 request.interceptors.response.use( response { const res response.data // 后端业务code约定: 200为成功, 其他为失败 if (res.code res.code ! 200) { ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { ElMessage.error(登录已过期请重新登录) router.push(/login) } else { ElMessage.error(网络异常请检查后端服务是否已启动) } return Promise.reject(error) } ) export default request关于baseURL这个参数写的是http://127.0.0.1:8000/api开发时Django后端就运行在8000端口前端Vite开发服务器默认5173端口这样两个服务是跨域的。跨域问题的解决不是在前端改什么配置而是在后端Django那边配置CORS_ALLOWED_ORIGINS这个我在第5章避坑部分会展开详细说。JWT ${token}这种写法使用的是Django REST Framework的JWT认证标准。JWT流程可以这样理解登录成功后后端返回一个字符串token前端保存在localStorage本地缓存中后续每次请求都在HTTP头里带上。后端通过校验这个token判断当前访问者是哪个用户、是否有权限执行当前操作。拦截器里同时响应了两个层面的错误请求抛出的网络错误和HTTP状态码错误。401是后端明确返回的“未认证”说明token缺失或者过期那就自动踢回登录页网络其他异常则提示检查后端有没有启动。这两条兜底逻辑能让联调时的报错快速定位到问题层面不至于前端黑屏后还要自己开浏览器开发者工具抓XHR请求一条条看。4.3 图书列表中一个值得复用的表格操作模式图书列表页是整个前端里最典型的CRUD组件——它把Element Plus的表格、分页、搜索框的组合用得非常常规但这种常规反而是毕业设计最需要的代码可读性高、每行逻辑都能解释、删改起来不费劲。template el-table :databookList v-loadingloading el-table-column proptitle label书名 width200/el-table-column el-table-column propauthor label作者 width150/el-table-column el-table-column propcurrent_stock label可借数 width100/el-table-column el-table-column label操作 width150 template #defaultscope el-button typedanger sizesmall clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table el-pagination current-changeloadBookList :current-pagepage :page-sizepageSize :totaltotal layouttotal, prev, pager, next /el-pagination /template script setup import { ref, onMounted } from vue import { ElMessageBox, ElMessage } from element-plus import request from ../api/request const bookList ref([]) const page ref(1) const pageSize ref(10) const total ref(0) const loadBookList async () { const res await request.get(/books/, { params: { page: page.value, page_size: pageSize.value } }) bookList.value res.data.rows total.value res.data.total } const handleDelete async (row) { // 先弹出确认框防止误删 await ElMessageBox.confirm(确定删除《${row.title}》吗删除后不可恢复, 警告, { type: warning }) await request.delete(/books/${row.id}/) ElMessage.success(删除成功) loadBookList() } onMounted(loadBookList) /scriptloadBookList函数设计时带了一个async/await原因是删除操作和弹窗提示都是异步任务。delete接口调用成功后会重新执行loadBookList刷新列表这样页面上的数据始终和后端数据库保持同步。这几行代码值得照抄到自己的项目里——图书删除这种危险操作前一定要加确认弹窗答辩现场如果误删了一条数据页面会留下非常明显的翻车现场。分页参数这块后端Django使用page和page_size两个参数来接收前端绑定到分页组件的el-pagination事件上。每次切换页码时组件触发事件重新请求数据表格自动更新。读者管理页基本就是这个页面的复制逻辑只改了字段和接口路径。5. 联调避坑Django 4.2 Vue 3 MySQL 8 的五个真实坑5.1 坑一MySQL 8 报错 caching_sha2_password 认证失败用MySQL Workbench或Navicat连接数据库时能连上但Django的python manage.py migrate或者系统启动后首次请求数据库时直接报错报错内容一般是Authentication plugin caching_sha2_password cannot be loaded。MySQL 8.0默认的认证插件改成了caching_sha2_password而某些Python驱动尤其是通过系统包管理器装的mysqlclient版本过旧还不认识这个插件。解决方式有两种推荐第一种在MySQL中创建系统专用账号时手动指定使用旧的mysql_native_password认证插件。CREATE USER library_userlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; GRANT ALL PRIVILEGES ON library_db.* TO library_userlocalhost; FLUSH PRIVILEGES;# settings.py 中对应的数据库配置 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: library_db, USER: library_user, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4} } }charset这里强烈建议选utf8mb4而不是utf8。utf8在MySQL里其实只支持最多三个字节的字符像生僻字和多数符号都会被截断或显示成乱码utf8mb4才是完整版UTF-8。另外HOST写成127.0.0.1不要写localhost某些Windows环境下Python的MySQL驱动解析localhost会去走IPv6导致连接超时这类偶发问题非常折腾人。5.2 坑二跨域配置写了但前端还是报CORS错误前端页面能打开但一调用接口浏览器控制台报错has been blocked by CORS policy于是去网上搜到解决方案——在Django里安装django-cors-headers然后把中间件加上把CORS_ALLOW_ALL_ORIGINS True配上。配完之后依然报错没变化。原因在于配置的顺序和位置。django-cors-headers要求中间件加在CommonMiddleware之前而且CORS_ALLOWED_ORIGINS和CORS_ALLOW_ALL_ORIGINS只需要设一个如果两个都写了CORS_ALLOWED_ORIGINS的空列表仍然会生效。同时还要检查这个配置是在整个Django运行中生效的如果改完settings没有重启runserver那当然是白改。# settings.py 中按这个顺序和写法来 INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 必须放在最上面 django.middleware.common.CommonMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]只开放Vite开发服务器的两个地址不要用CORS_ALLOW_ALL_ORIGINS True图省事。生产环境如果前后端部署在同一台机器的不同端口跨域配置也是用白名单方式开放指定域名的权限。5.3 坑三逾期天数永远比实际少一天还书页面显示的逾期天数读者明明逾期还了2天页面却显示1天有时候上午还书还显示逾期0天实际已经超期了。排查了好一阵代码逻辑都没问题最后发现是时区问题。数据库里存的时间是UTC时间Django的TIME_ZONE设置成了UTC而中国时间比UTC早8小时导致timezone.now()取到的时间比本地时间晚了8小时。# settings.py TIME_ZONE Asia/Shanghai USE_TZ TrueUSE_TZ True时Django内部会用UTC时间做逻辑运算但写入数据库时会自动转换到Asia/Shanghai时区。改了这一个配置后后端接口返回的due_time如果还是UTC字符串做时间比较时也要注意。全套系统如果前后端都在本机运行前端直接用后端格式化好的%Y-%m-%d %H:%M:%S字符串就好不要自己在前端手动new Date()再转换时区容易二次翻车。5.4 坑四Django后台加载不到CSS样式输入http://127.0.0.1:8000/admin/之后能打开登录框但页面完全没有样式只有一堆堆叠的文字链接浏览器开发者工具里满屏的404错误找不到/static/admin/css/下的CSS文件。原因在于Django的静态文件服务默认只在DEBUGTrue时开启但依赖的django.contrib.staticfiles没被正确配置到INSTALLED_APPS里或者STATIC_URL和项目实际的静态文件目录不一致。# settings.py 中检查这几项 INSTALLED_APPS [ django.contrib.staticfiles, # 必须存在 ... ] STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, # 如果项目有自定义static目录 ]如果用的是开发模式跑runserver只要INSTALLED_APPS里有django.contrib.staticfilesDjango会自动帮你处理/static/admin/下的文件不需要再用额外的whitenoise库。如果还是404检查一下是不是runserver没有把端口开在正确位置或者浏览器缓存了旧的404响应强制刷新一次再试。5.5 坑五ORM建表后新增字段报已存在改模型加了一个字段执行python manage.py makemigrations和migrate后提示Duplicate column name去MySQL里看那张表发现字段已经加上了但migration记录里没写进去导致下次迁移重复执行。由于数据库脚本是直接用SQL文件导入的Django的migration表里没有任何记录Django不知道这个表已经存在了。进入MySQL删除这张表的僵尸记录-- 把library_db换成你自己项目的库名 USE library_db; -- 查看migration记录中是否有对应app的迁移 SELECT * FROM django_migrations WHERE app books; -- 如果没有手动插入一条最新的迁移记录替换migration文件名 INSERT INTO django_migrations (app, name, applied) VALUES (books, 0001_initial, NOW());最省事的做法是如果系统里还没有业务数据直接删库重建重新执行包里的library.sql再执行python manage.py migrate --run-syncdb让Django根据模型补建不存在的表最后把migration记录对齐。有过一次半路续接的migration状态之后后面的每次模型修改都要先确保makemigrations能执行。5.6 避坑经验汇总联调前必过的自查清单检查项正确配置出错现象MySQL认证插件使用mysql_native_passwordPython连接时报caching_sha2_passwordCORS中间件顺序CorsMiddleware在CommonMiddleware前前端请求被CORS拦截时区设置TIME_ZONE Asia/Shanghai逾期天数差8小时静态文件INSTALLED_APPS含staticfilesAdmin后台无样式迁移记录django_migrations与模型一致migrate报Duplicate column自查清单主要解决的是“资源跑起来之后换到了新环境/新电脑上能不能重复运行”的问题。答辩前进行一次这些检查项的走查能节省大量现场排错的时间也能在演示前自己发现大部分翻车风险。第6章我将讲已验证没问题的系统在答辩现场的呈现顺序和讲解节奏。6. 答辩演示前的最后验证拿这套项目走一遍完整说辞6.1 全流程走查从浏览器到数据库的一条龙演示答辩演示时最怕的不是评委问技术问题而是演示到一半现场翻车——比如借书时报库存不足或者页面加载失败。我自己每次都用一条固定的路径走查启动顺序固定、数据准备固定、说辞固定。先从MySQL开始启动数据库如果是Windows服务就确认服务已在运行接着启动Django后端确认http://127.0.0.1:8000/admin/能打开最后启动Vite开发服务器用管理员账号登录系统。登录成功后的演示顺序也很关键。建议先打开图书列表页演示分页和搜索功能然后新开一个标签页打开读者列表用到系统里的预置读者账号再回到借还管理页输入读者编号调出读者信息输入图书编号完成一次借书最后进入逾期统计页展示当前逾期记录的列表。整个流程覆盖了系统最核心的增删改查和统计功能前后不过五分钟。数据准备上有一点自己踩过坑演示前故意准备一本库存只有1的图书用来演示借书演示后立刻还回去恢复正常数据。如果你选了一本库存较多的大众图书展示借书那么借完也要在下一个环节把它还掉否则后续演示库存列表时要注意那本书显示少了1本答辩评委会认为数据对不上。这套系统预置的演示数据平衡了借出和未借出的馆藏状态直接拿来走查即可。6.2 用自己的话说清一段评委最可能追问的代码答辩时评委大概率会指着借阅记录表的status字段问“你这个逾期是怎么判断的系统根据哪个字段来识别”此时直接回答borrow_record表里有一个due_time字段是借书时后端用当前时间加30天计算出来的还书时不直接改due_time而是对比当前时间和due_time的大小关系把比较结果写进status字段2就是逾期归还、1是正常归还。同时描述一下库存在这个过程中怎么保持一致借书时current_stock减一还书时加一加减放在同一个数据库事务里不会出现只减不加或只加不减的状态不一致异常。这类儒雅随和的表述方式比直接背代码的效果好很多。讲完一段之后还可以补一句如果要扩展成预约借书功能则可以在status中追加一个3代表预约待取状态并在借书接口里增加预约信息的校验。这样把当前设计和可扩展性同时说清楚了比单纯讲“这里我用了一个整数来表示状态”要有说服力得多。从那以后我每次拿到一套类似的项目都会先强制自己走一遍完整流程确认环境能跑、确认关键业务链路能通、确认重要字段在答辩时能讲清然后才去动代码。这套流程习惯省掉了我很多次踩坑后半夜查配置的折腾。希望帮到你。本文还有配套的精品资源点击获取