资讯动态

SpringBoot2+Vue3图书馆管理系统实战:从数据建模到前后端部署

发布时间:2026/9/25 3:36:29 来源:尧图企业网站定制
前两年做图书馆相关项目时我接手了一个很典型的业务场景疫情影响下图书馆恢复开放但借阅方式全变了——读者不能直接进书库翻找要预约入馆、要控制同时在馆人数、借书还书要尽量无接触管理员还要登记读者健康信息。传统的“图书增删改查”三板斧系统根本扛不住这些需求于是我用 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 从零搭了一套完整的图书馆管理系统前后端分离包含读者端和管理员端还配了一份完整的项目文档。这篇文章就把这套系统的核心设计和实现过程完整复盘一遍。无论你是正在做毕业设计的学生还是想通过一个全栈项目把 Vue3 和 SpringBoot2 串联起来的开发者都可以直接参考。我会把数据库怎么设计、预约借还的状态机怎么流转、MyBatis-Plus 在真实项目里怎么配置、Vue3 工程怎么落地以及我在联调部署阶段踩过的坑全部摊开来讲。1. 疫情下的图书馆管理系统需求清单和模块边界1.1 传统图书管理系统的三个短板以前常见的图书管理系统核心就围绕“书”展开管理员录入图书、读者检索、办理借书还书再高级一点做个超期罚款。但在特殊时期这套逻辑有几个明显的断点。第一没有预约机制。读者到了图书馆才发现书被别人借走了或者发现馆内人多到需要限流只能白跑一趟。疫情场景下预约是刚需既包括“预约某本具体图书”也包括“预约某个入馆时段/座位”。第二缺少健康信息登记环节。入馆读者需要登记体温、健康状态、联系方式方便后续追溯。传统系统没有这个字段设计临时用纸质表格又无法和借阅记录关联。第三借阅流程不够无接触。理想状态是读者在线上确认借阅管理员把书放到指定位置读者直接取走减少在封闭空间里的停留时间。这需要系统提供“预约借书-书籍预留-线下取书”的完整状态流转而不是简单的“借出/归还”两个状态。1.2 系统功能模块全景这套系统的整体模块划分如下模块面向对象核心功能读者认证读者端注册、登录、JWT鉴权、个人信息维护图书检索读者端分类浏览、关键词搜索、库存查看、预约借书座位/入馆预约读者端选择时间段、预约座位、查看预约记录、取消预约健康信息登记读者端入馆前提交体温和健康状态形成每日台账借阅管理管理员端借书核验、还书处理、续借、超期记录图书管理管理员端图书增删改查、分类管理、库存调整、ISBN导入预约审核管理员端审核借书预约、图书预留、取消处理读者管理管理员端读者列表、借阅历史、信用状态、账号冻结数据统计管理员端借阅量统计、热门图书、在馆人数实时报表系统设置管理员端管理员账号、角色权限、开馆时段配置读者端和管理员端通过 RESTful API 交互前端是 Vue3 单页应用后端是 SpringBoot2 项目数据库统一走 MySQL8.0。整个系统不是简单的 CRUD 堆积而是围绕“预约-限流-无接触”这条业务主线设计的。1.3 一次完整借阅的流程闭环我举一个最典型的场景大家就能理解这套系统的核心链路读者登录系统检索到一本需要的书发现“可借但库存紧张”于是提交预约借书申请同时填写入馆时间段和健康信息。管理员在后台看到预约申请审核通过后系统为读者保留这本书锁库存。读者按时到馆测温正常后扫码签到到指定位置取书。管理员在后台确认“已取书”预约记录变成借阅记录库存真正减一。到期前系统自动提醒读者线上续借或在馆内无接触还书归还后库存加一整个闭环就结束了。这个闭环里的每一步都需要数据库表和状态字段去支撑所以在写代码之前我花了大量时间在数据建模上。2. 技术选型拆解SpringBoot2 Vue3 MyBatis-Plus 这套组合赢在哪儿2.1 SpringBoot2 为什么是当前最稳妥的选择做项目之前我纠结过要不要直接上 SpringBoot3最终选了 SpringBoot2原因是实际环境决定的SpringBoot3 强制要求 JDK17 及以上而很多教学环境、服务器和生产项目还普遍停留在 JDK8 或 JDK11另一方面SpringBoot2 经过多年迭代生态非常完整网上能查到的资料、POM 依赖版本、中间件兼容性都比较成熟对于毕业设计或中小型业务系统来说完全够用。我用的关键依赖版本是SpringBoot 2.7.x、MyBatis-Plus 3.5.x、MySQL Connector/J 8.0.x、Lombok 1.18.x、JWT 0.9.x或 jjwt 0.11.x。这个组合在 Maven 仓库里都是稳定版本不会有莫名其妙的依赖冲突。2.2 MyBatis-Plus 相比 Spring Data JPA为什么更适合国内业务系统这是一个我在项目里经常被问到的问题。两者都很好但 MyBatis-Plus 在国内业务系统里明显更顺手原因有三个。第一SQL 可控。JPA 把大量 SQL 生成逻辑封装在方法命名和 Hibernate 底层遇到复杂多表查询、统计报表时要么写 JPQL要么用原生 SQL调试成本高。MyBatis-Plus 既提供QueryWrapper做简单的条件拼装又保留了 XML 自定义 SQL 的口子复杂查询想怎么写就怎么写。第二分页插件直接可用。JPA 的Pageable确实也很方便但 MyBatis-Plus 的PaginationInnerInterceptor配合selectPage写起来更符合国内开发习惯而且对 MySQL 的分页优化处理更直接。第三代码生成器。MyBatis-Plus 的AutoGenerator能根据数据库表一键生成 entity、mapper、service、controller前端管理系统的开发节奏本来就是以表和接口为中心的生成后进行二次修改效率提升非常明显。我用一张表对比一下两者在各维度的差异对比维度MyBatis-PlusSpring Data JPASQL可控性高支持XML和Wrapper中复杂查询需要JPQL或原生SQL学习曲线平缓核心看Wrapper用法较陡需要理解实体映射和懒加载动态条件拼装QueryWrapper非常方便需要Specification或QueryDSL辅助分页内置插件简单直接Pageable使用也很方便代码生成官方AutoGenerator齐全需额外配置Spring Data REST等工具团队维护习惯国内大多数Java团队熟悉偏国外技术栈习惯2.3 Vue3 组件化开发的前后端分离优势前端为什么选 Vue3 而不是 Vue2我考虑的主要有几点组合式 API 让代码组织变得更灵活同一个功能的业务逻辑可以集中在一个setup函数里TypeScript 支持更好对于这种业务字段比较多的管理系统类型声明能提前暴露数据结构错误Element Plus 只支持 Vue3而 Element UI 停留在 Vue2管理后台的表格、表单、弹窗、树形控件直接用 Element Plus 非常省事。Vue3 另一个实用优势是v-model多绑定和组件的Teleport在实现借阅弹窗、批量导入弹窗、全屏预览这些交互时代码比 Vue2 干净很多。项目里我还用到了 Vue Router 4 做路由守卫、Pinia 代替 Vuex 做用户状态管理。这套组合现在已经是 Vue3 项目的标准配置了。2.4 MySQL8.0 的版本红利选 MySQL8.0 最大的理由是它的默认字符集就是utf8mb4中文和 emoji 表情都能直接存不用像 5.7 一样每次建库都要单独指定。其次8.0 的窗口函数ROW_NUMBER、RANK等在做借阅排名、热门图书统计时非常方便一条 SQL 就能搞定原本要写一堆 Java 循环的排名逻辑。另外8.0 对索引、查询优化器的改进明显随着数据量上来性能比 5.7 更稳。不过 MySQL8.0 也有需要适应的点最典型的是默认认证插件改成了caching_sha2_password旧版 Navicat、老版本 JDBC 驱动直接连不上这个问题我会在后面的部署章节单独展开。3. 数据库建模从图书档案到预约台账一次说清楚3.1 核心表的职责划分数据库设计是整个项目的地基。我没有把所有字段塞进一两张表而是按照业务域拆分图书域book图书主表、book_category分类表、book_stock_log库存变更流水用于追溯每一次库存变动读者域reader读者表含账号、密码、联系方式、信用状态借阅域borrow_record借阅记录、reserve_record预约记录、fine_record超期费用记录场馆域seat座位/入馆时段配置、seat_reserve座位预约记录健康与安全域health_check每日健康台账、visit_log入馆/出馆记录系统域admin管理员、role角色、notification站内消息这里要特别提醒book_stock_log和visit_log这种流水表一开始很容易忽略后来做统计和排查问题时会非常痛苦。比如库存总数对不上账如果没有流水表你根本不知道是哪个环节出了问题。3.2 预约-借阅-归还的状态机设计预约和借阅的状态如果只用“0/1”两个数字表示后面一定会被业务折腾到崩溃。我把reserve_record和borrow_record设计成状态机模型每个状态都严格定义预约记录的核心状态状态值状态含义可流转到WAITING排队中等待管理员审核CANCELLED、AVAILABLEAVAILABLE审核通过书籍已预留BORROWED、EXPIREDBORROWED已取书转为借阅记录RETURNEDEXPIRED预留超时未取自动释放CANCELLEDCANCELLED取消/释放无借阅记录的核心状态状态值状态含义可流转到BORROWED借出中RENEWED、RETURNED、OVERDUERENEWED已续借RETURNED、OVERDUEOVERDUE已超期RETURNED、FINERETURNED已归还无在 Java 代码层面我没有用一堆 if-else 去控制流转而是写了一个状态转换校验器每个状态迁移都显式声明不合法操作直接抛业务异常。这样前端无论怎么调接口后端都不会产生脏数据。比如读者已经取消预约的图书永远不可能被管理员误确认成“已取书”。3.3 在馆人数限流的座位预约设计疫情期间限流是刚需我的做法是设计seat_reserve表按天和时段控制名额CREATE TABLE seat_reserve ( id BIGINT PRIMARY KEY AUTO_INCREMENT, reader_id BIGINT NOT NULL, seat_no VARCHAR(20), reserve_date DATE NOT NULL, time_slot VARCHAR(10) NOT NULL COMMENT 例如 09-12, 13-16, status TINYINT DEFAULT 0 COMMENT 0待签到, 1已签到, 2已取消, 3已过期, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_reader_date_slot (reader_id, reserve_date, time_slot), KEY idx_reserve_date_slot (reserve_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键点在于UNIQUE KEY uk_reader_date_slot数据库层面保证同一个读者在同一天同一时段只能有一条预约记录防止并发下的重复提交。同时在服务层用 Redis 或者数据库行锁维护“当前时段剩余名额”提交预约时用原子操作扣减避免有些同学直接查库存再更新的并发问题。3.4 健康信息登记表的字段设计健康台账表我设计得比较简单但字段类型和索引都有讲究CREATE TABLE health_check ( id BIGINT PRIMARY KEY AUTO_INCREMENT, reader_id BIGINT NOT NULL, temperature DECIMAL(4,1) NOT NULL, health_status TINYINT DEFAULT 0 COMMENT 0正常, 1异常, contact_phone VARCHAR(20), check_time DATETIME NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_reader_time (reader_id, check_time), KEY idx_check_time (check_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;temperature用DECIMAL(4,1)而不是FLOAT是因为浮点数在存储和比较时会有精度误差体温这种需要精确到小数点一位的数据要避免用浮点。idx_reader_time这个联合索引是为了支持“查某个读者某天的登记记录”和“根据时间段批量导出某日台账”这两种查询直接命中索引不用全表扫描。4. 后端实现笔记SpringBoot2 怎么把 MyBatis-Plus 用得顺手4.1 工程结构与 MyBatis-Plus 配置后端工程我采用经典的 Maven 单模块结构包含controller、service、mapper、entity、config、common统一返回结果和异常处理这几层。没有强行拆多模块毕业设计和中小型项目拆多模块反而增加维护成本。MyBatis-Plus 的核心配置首先要加分页插件很多新手忘了这一步直接调selectPage结果分页完全不生效Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }其次是逻辑删除的配置。图书、读者这些主数据不建议物理删除一删就丢历史记录。我的做法是给所有核心表加deleted字段然后在application.yml里统一配置mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 mapper-locations: classpath*:mapper/**/*.xml这样所有的selectList、selectPage都会自动追加WHERE deleted 0删除操作自动变成UPDATE ... SET deleted 1业务代码完全无感知。4.2 用 MyBatis-Plus 实现动态检索与分页查询图书检索是读者端最高频的接口支持按书名、ISBN、分类、作者、状态组合查询还要分页。用 MyBatis-Plus 的LambdaQueryWrapper可以非常干净地处理动态条件不用拼 SQL 字符串public PageResultBookVO searchBooks(BookQuery query) { LambdaQueryWrapperBook wrapper Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(query.getBookName()), Book::getBookName, query.getBookName()); wrapper.eq(StringUtils.hasText(query.getIsbn()), Book::getIsbn, query.getIsbn()); wrapper.eq(query.getCategoryId() ! null, Book::getCategoryId, query.getCategoryId()); wrapper.eq(query.getStatus() ! null, Book::getStatus, query.getStatus()); wrapper.orderByDesc(Book::getCreateTime); PageBook page bookMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper); return PageResult.of(page); }这里有个经验点lambdaQuery里的布尔条件StringUtils.hasText(...)是 MyBatis-Plus 的“条件增强”条件不满足时该条件直接不拼写起来比手动拆 if 判断清爽太多。另外分页对象Page的泛型建议直接写成实体类这样page.getRecords()返回的就是实体列表不需要再手动转类型。4.3 JWT 登录鉴权的完整代码骨架管理员端和读者端我都用的 JWT 做无状态鉴权登录成功生成 token前端请求时放到Authorization头。生成 token 的核心逻辑如下public String createToken(LoginUser user) { return Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .claim(role, user.getRole()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }然后写一个拦截器在 SpringBoot2 里注册到WebMvcConfigurerComponent public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 放行登录、注册等白名单其余校验token并解析用户信息 return true; } }我建议把“解析 token 得到当前用户”的逻辑做成CurrentUser注解 HandlerMethodArgumentResolver这样 controller 里可以直接拿到当前登录用户对象不用每个方法都手动解析一遍。这种方式在团队协作里非常受欢迎接口签名也干净。需要注意 JWT 的过期时间不宜过长我一般设 24 小时。对于需要“记住我”的读者端前端配合刷新 token 的接口做静默续期而不是把 JWT 过期时间拉长到一个星期——过期时间越长token 泄露后的风险窗口就越大。4.4 XML 文件与 Mapper 接口不在同一目录的坑这个坑我印象特别深。MyBatis-Plus 的 Mapper 接口默认会扫描同包下的 XML但很多项目习惯把 XML 放在src/main/resources/mapper/下此时接口和 XML 分属不同路径如果不做配置运行时就会报Invalid bound statement (not found)。正确的做法分两步。第一步在application.yml里声明 XML 位置mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml第二步在pom.xml的build节点里把 XML 目录加入资源过滤否则打包成 jar 后 XML 不会被包含进去resources resource directorysrc/main/resources/directory /resource resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources第二步是特别多人踩的坑。本地 IDEA 跑着没问题一打 jar 包部署到服务器就报找不到 SQL就是因为 XML 没被扫进 jar。src/main/java下的 XML 默认不参与打包必须用resources显式声明。如果你按我的习惯把 XML 统一放到resources/mapper/则只需要配置第一步即可但pom.xml里的资源声明建议保留双保险。5. 前端 Vue3 工程落地从 Vite 脚手架到对接后端5.1 创建工程与依赖安装前端我用的 Vite 脚手架命令如下npm create vitelatest library-frontend -- --template vue cd library-frontend npm install npm install element-plus axios pinia vue-router4创建完工程后我会先把目录结构调整成适合管理系统的结构src/ api/ // 按模块拆分的接口定义 assets/ components/ // 公共组件表格、弹窗、分页 layout/ // 后台主布局侧边栏顶栏 router/ // 路由配置与守卫 stores/ // Pinia 状态管理 views/ // 页面组件 utils/ // axios 封装、工具函数Element Plus 建议按需引入而不是全量引入。全量引入在开发时很爽但打包体积会增加很多。我使用unplugin-vue-components和unplugin-auto-import配合实现按需加载配置一次之后模板里直接写el-table等组件就能自动引入对应样式。5.2 组合式 API 的页面写法对比Vue3 的 Composition API 对管理系统来说最大的好处是同一个页面的搜索条件、表格数据、分页参数、操作函数可以放在一起组织不再像 Vue2 那样分散在data、methods、watch各个选项里。举一个管理员端图书列表页的核心写法script setup import { ref, onMounted } from vue import { getBookList, deleteBook } from /api/book const loading ref(false) const bookList ref([]) const total ref(0) const queryParams ref({ pageNum: 1, pageSize: 10, bookName: }) async function loadData() { loading.value true try { const res await getBookList(queryParams.value) bookList.value res.rows total.value res.total } finally { loading.value false } } function handleSearch() { queryParams.value.pageNum 1 loadData() } function handleDelete(row) { // 确认弹窗后调用删除接口 } onMounted(loadData) /script对比 Vue2 的 Options APIscript setup写法少了大量this上下文问题逻辑也更内聚多人合作时改动互相冲突的概率小。对于管理系统这种大量“列表搜索弹窗表单”的页面这个写法几乎是效率最优解。5.3 axios 二次封装与登录态管理axios 封装是所有前端项目绕不开的一步。我封装了统一的请求实例做了三件事请求头自动带 token、统一处理 HTTP 错误状态码、根据后端返回业务码做全局提示和登录过期跳转。import axios from axios import { ElMessage } from element-plus import router from /router const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token) router.push(/login) } else { ElMessage.error(res.msg || 请求失败) } return Promise.reject(new Error(res.msg)) } return res }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default service登录态管理用 Pinia 存用户信息和权限页面刷新时再从 token 解析或调/user/info接口重新拉取。这里有一个实践经验用户信息不要只放 localStorage刷新后要主动重新请求后端接口保证角色和权限始终是最新的。5.4 Vue3 开发中几个容易踩的小坑说几个我实际遇到过的 Vue3 问题。第一个是v-model在自定义组件上的多绑定变化。Vue3 里v-model对应的 prop 从value变成了modelValue事件从input变成了update:modelValue。如果你在 Element Plus 基础上自定义封装表单组件很容易因为没改这个约定导致双向绑定失效。Vue3 还支持v-model:titlexxx这种多参数写法一个组件同时绑多个值非常方便这是 Vue2 没有的。第二个是路由懒加载导致的“偶发白屏”。管理系统页面多了以后我会把路由全改成动态导入() import(/views/xxx.vue)。但要注意如果某个组件的异步加载时间过长或网络抖动首次跳转时可能白屏。解决办法是在路由配置里加全局的beforeEach配合页面加载进度条让用户知道页面正在加载同时用 Vite 的build.rollupOptions合理切分 chunk不要把所有页面打成一个巨大的 bundle。第三个是 Vite dev 模式局域网访问空白。有一次我在演示项目手机通过局域网访问笔记本的 Vite 服务页面直接空白。排查后发现 Vite 默认只监听localhost需要在vite.config.js里配置export default defineConfig({ server: { host: 0.0.0.0, port: 5173 } })同时还要留意如果你在 Edge 或 Chrome 里遇到页面能打开但右上角关闭按钮异常之类的问题大部分时候是浏览器扩展或硬件加速导致跟项目代码没关系别浪费时间往业务代码上排查。6. 部署与联调MySQL8.0 的安装坑和前后端联调注意事项6.1 8.0 装完连不上的三个原因MySQL8.0 安装本身不复杂但装完连不上是高频问题我总结三个最常见的原因。第一是认证插件问题。8.0 默认使用caching_sha2_password如果你的 JDBC 驱动是 5.x 的老版本或者 Navicat 版本太旧就会报Unable to load authentication plugin caching_sha2_password。解决办法是升级驱动到mysql-connector-java8.0.x并在连接 URL 加上allowPublicKeyRetrievaltrue参数。第二是时区问题。连接 URL 里如果不指定serverTimezone会报The server time zone value is unrecognized。我在application.yml里的数据库连接配置是完整的spring: datasource: url: jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver第三是防火墙和远程访问权限。如果不是本机连接MySQL 默认只允许localhost登录。需要在服务器上创建远程用户并授权还要确认安全组和防火墙放行了 3306 端口。安装时如果提示Access denied for user rootx.x.x.x就去 MySQL 里执行CREATE USER library% IDENTIFIED BY 密码; GRANT ALL PRIVILEGES ON library.* TO library%; FLUSH PRIVILEGES;不建议直接给 root 开放远程权限独立业务账号更安全也方便后续出问题回溯。6.2 前端代理配置与跨域开发环境下前后端分别运行在 5173 和 8080 端口直接请求必然跨域。我的处理方案是前端 Vite 配置代理让请求在开发阶段就走同源export default defineConfig({ server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端里的请求地址统一写成/api/xxxaxios 的baseURL也配置成/api浏览器看到的所有请求都是同源的不需要后端开启 CORS。生产环境则通过 Nginx 反向代理把/api转发到后端服务前端的直接托管静态文件代码里不用写死任何服务器 IP。6.3 打包部署时 SpringBoot 打着包找不到主类本地 IDEA 运行正常执行mvn package后java -jar却提示找不到主类十有八九是pom.xml里没配 Spring Boot Maven 插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build没有这个插件打出来的就是一个普通 jar不包含依赖也不可能定位主类。加上插件后默认会打包成可执行 fat jar同时保留Main-Class。另外启动命令里建议加--spring.profiles.activeprod区分开发和生产配置我习惯准备application-dev.yml和application-prod.yml数据库地址、日志级别各自独立上线时只改一个参数不用动代码。7. 拿到这份源码之后怎么把它变成你自己的项目7.1 建议你重点改的三个地方很多同学拿到一套源码喜欢从头到尾读一遍代码但效率很低。我的建议是拿到源码后重点改三个地方改完基本就对这个项目有掌控力了。一是改数据库表结构加两个你自己的业务字段。比如在book表增加一个“推荐指数”字段在reader表增加一个“读者积分”字段然后从前端页面到后端接口完整地加一遍增删改查。这个过程能让你立刻理解一套代码从前端到后端再回到数据库的通路是怎么走通的。二是改 JWT 鉴权的默认密钥。源码自带的SECRET_KEY一定要换掉这个属于安全敏感配置。同时建议把 token 过期时间、会话时长都改成一个你认为合理的值体现出你对业务的理解。三是按你自己的需求扩展一个统计报表页面。比如统计每个月的借阅量、热门图书 Top10、各分类占比用 MySQL8.0 的窗口函数做查询后端聚合数据后前端用 ECharts 或 Element Plus 的统计组件展示。这非常能打答辩或面试提问时也容易讲清楚。7.2 我做完这个项目后的几点体会做完这套系统我个人最大的感受是疫情场景下的管理系统难点不在于单个功能有多复杂而在于把“预约-限流-无接触”这条异常链路在数据层面设计得闭环。举个小细节设计预约状态机的时候如果只按“待处理/已通过/已拒绝/已取消”四个状态来设计后面就无法处理“预留超时自动释放”这种真实场景。业务上测试的时候才会发现状态流转少一个EXPIRED预留的图书就可能被一直锁着库存永远出不来。所以状态枚举宁可一开始多设计也不能临时拆东墙补西墙。这个经验后来我用在了所有带状态流转的业务上收益非常大。另一个体会是前后端分离的项目联调时接口文档的规范比代码本身更重要。我在项目里用统一的Result返回结构不管成功失败都返回{ code, msg, data }前端 axios 封装只依赖这一个结构。只要后端接口返回格式统一了前端很多重复代码都能砍掉联调效率翻倍。最后想说如果你是为了学习不要只满足于把系统跑起来。花一晚上把borrow_record和reserve_record之间的关系梳理清楚再亲手删掉一条借阅记录试试所有关联数据的变化这种对数据链路的感觉比看十遍教程都有用。这套源码如果拿来做毕业设计我建议你先跑通核心借阅闭环再逐步增加统计报表和权限控制这些加分功能稳扎稳打比一次性把需求堆满要稳妥得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑