前后端分离的图书管理系统,SpringBoot+Vue+MyBatis+MySQL这套组合应该算是Java Web开发里最经典的就业级配置了。我前阵子刚好把一个带完整源码和部署教程的项目从头到尾跑通了一遍,今天就把整个思路、踩坑记录、配置细节全部摊开聊一聊,给正在做课程设计或者准备面试项目的小伙伴一个可以直接参考的底稿。
这个项目表面上是个图书管理系统,但核心看点其实是三个:第一,前后端完全分离,前端Vue负责页面交互,后端SpringBoot提供RESTful接口,两者通过JSON格式的数据通信;第二,业务场景放在特殊时期的图书馆管理需求下,涉及预约、限流、无接触借还这样的功能设计;第三,整个项目自带MySQL数据库脚本和IDEA部署说明,照着一步步操作就能跑起来。对于正在学Java Web、准备毕业设计或者想补充一个完整全栈项目经验的人来说,这套源码的学习价值很实在。
1. 项目整体架构与技术选型拆解
1.1 前后端分离:为什么是SpringBoot+Vue这个组合
我先说结论:在中小型管理系统这个领域,SpringBoot+Vue是当前生态最成熟、资料最多、求职认可度也最高的搭配之一。
很多人一开始会纠结,为什么不用传统的Thymeleaf模板引擎,把页面和后端逻辑写在同一个工程里?那样不是更简单吗?确实,单体应用配合模板渲染,开发起来更快,但对于这个项目来说,前后端分离有几个实实在在的好处。
第一点是职责清晰。后端只负责处理业务逻辑、校验数据、操作数据库,返回JSON数据;前端只负责渲染页面、绑定事件、调用接口。两边可以并行开发,互不阻塞。比如前端同学可以先拿Mock数据写页面,后端同学同时写接口,最后再联调,效率明显更高。
第二点是复用性。后端接口一旦写好,不仅Vue可以用,将来如果想出小程序端、移动端,或者管理人员想做一个简易的桌面客户端,接口都可以直接被复用,不需要重新写一套业务逻辑。
第三点是部署灵活。前端打包成静态文件,可以扔到Nginx里;后端打成Jar包独立运行,两边可以单独扩容。对于一个图书馆管理系统来说,可能不太需要极致的性能,但这种架构思路是通用的,学一次受用终身。
SpringBoot这边,它简化了Spring的配置流程,自带Tomcat,内嵌Web容器,配合MyBatis操作数据库很顺滑。MyBatis的优势在于SQL自己掌控,复杂查询写XML很直观,对Java开发者来说这种半ORM的方式更好理解SQL是怎么执行的,排查问题也方便。MySQL则是开源数据库的事实标准,轻量、稳定,教学场景和企业实践都够用。
1.2 这个项目的业务侧重点:特殊时期图书馆管理场景
标题里特意加了“疫情下”这个限定词,可能有人觉得无所谓,不就是一个普通的图书管理系统吗?其实这里的业务设计是有讲究的。
特殊时期的图书馆管理,核心需求发生了几个变化。第一,读者不能随意进出图书馆,需要提前预约到馆时间段,馆内需要控制同时人数;第二,借书还书要尽量减少接触,预约借阅和自助消毒的流程要打通;第三,管理员需要能查看读者的健康状态信息,作入场核验;第四,逾期处理和催还通知没办法线下进行,系统层面的消息提醒变得更重要。
这些需求就意味着,普通的图书管理系统只有“用户-图书-借阅”这三张表是不够的,还需要增加预约记录表、场馆限流配置、健康信息登记这样的逻辑。这也是我为什么觉得这套源码值得看的原因——它不只是把CRUD做完,而是把业务场景真正映射到了数据结构和接口设计上。
当然,对于初学者来说,这些额外功能也增加了理解成本。但如果能把预约和限流这部分逻辑搞定,整个项目的含金量会明显提升一个档位,面试的时候也更有东西可以讲。
1.3 数据表设计与核心模块划分
在设计数据库时,我建议先画清楚实体关系,再动手建表。这个项目里主要的实体包括:用户(读者和管理员)、图书分类、图书信息、借阅记录、预约记录、健康登记信息。
用户表的基本字段是id、用户名、密码、姓名、学号/工号、角色、手机号、状态。密码建议存加密后的值,前端向后端传输密码时不要明文直接落地到数据库。角色字段一般用字符串定义,比如admin和user,也可以用一个int类型字段,用0和1区分,看团队习惯。
图书表要区分“图书档案”和“馆藏副本”,一本书可能有五本副本,每本副本有独立的可借状态,这是最容易被新手忽略的地方。如果只有一本书的信息而没有副本概念,那同一本书被两个人同时预约时状态就会变成一团乱麻。这套源码里我看到是把book表和book_copy的概念做了区分的,虽然实现上可能简化成在图书信息表里加一个库存数量字段,但思路是对的。
借阅记录表是业务核心,要记录哪本书、哪个用户、什么时候借、什么时候还、状态是什么。这个表还要关联预约记录,比如用户预约了某本书,管理员找到书后,把预约状态改成可取书,读者到馆后扫码取书,系统自动生成借阅记录。
健康登记表是特殊时期场景下的附加模块,记录体温、健康码状态、到馆时间段等信息,供管理员核验。这个功能实现起来其实就是简单的CRUD,但表设计的时候要注意加时间戳,方便追溯历史记录。
除此之外,管理端还有公告发布、借阅排行榜、逾期统计这些模块,基本都是基于上述主表的聚合查询,在SQL里做统计就行。
2. 核心功能与业务逻辑的关键实现
2.1 登录与权限控制:从Session到JWT
前后端分离的项目里,登录鉴权是最绕不开的一环。传统单体应用里用Session,浏览器自动携带Cookie,后端在Session里存用户状态,这套方案开发简单,但前后端分离后问题就开始出现了。
跨域情况下Cookie的携带规则变复杂了,需要配置CORS的credentials,而且后端做集群的话Session还得考虑共享问题。所以现在主流的做法是用JWT或者Token方案。
这套项目采用的方案是这样的:用户输入用户名密码,前端调用后端的登录接口,后端校验用户名密码成功后,生成一个Token返回给前端。前端拿到Token后存到localStorage里,之后的每一次请求,在Axios的请求拦截器里把Token塞进请求头的Authorization字段。后端用一个拦截器统一拦截需要登录的接口,解析Token,确认用户身份和角色,再放行请求。
这个流程我第一次跑通的时候觉得特别清爽,因为后端接口逻辑里完全没有Session的操作,每个接口都只需关注自己的业务逻辑,身份认证被抽成了一个横切关注点。新手如果之前只写过传统单体应用,一定要把这一块的代码仔细看一遍,这几乎是目前企业级项目的主流模式。
权限控制方面,管理员的接口和数据接口要做区分。比如删除图书、审核预约、查看所有用户列表这些接口,只允许admin角色访问;普通用户只能操作自己的预约和借阅记录。Spring Boot里面可以用拦截器配合注解实现,也可以用Spring Security做更细粒度的控制。这个项目用的是拦截器方案,我感觉对学习来说反而更友好,能把整个鉴权链路看得明明白白,后面想升级到Spring Security也顺理成章。
2.2 预约借阅与限流机制:业务流程怎么落地
特殊时期图书馆管理最核心的特征,就是把“直接到馆借书”改成“线上预约+线下核销”。完整的业务闭环大概是这样的:
用户登录系统后,先查看图书列表,确认想借的书还有可借的库存。如果有库存,就可以发起预约,选择想到馆取书的时间段。系统校验该时间段剩余名额,如果人数没有达到馆内限流上限,就创建一条预约记录,状态为“待处理”。管理员在后台看到待处理预约后,根据馆藏情况确认预约,确认后状态变为“可取书”。用户到馆后,管理员核对身份和健康登记信息,点击核销,系统自动生成借阅记录。用户在规定时间内归还图书,管理员确认还书,借阅记录状态变为“已归还”,库存数量加一。
这里面有几个细节值得关注。第一是谁来扣减库存。正确的方式是在确认预约而不是创建预约的时候扣减库存,否则用户预约后不来取书,库存就会一直被占用。第二是超时处理。预约确认后用户超过X天不来取书,应该取消预约并释放库存,这个可以用定时任务实现。第三是限流。场馆容量是有限制的,同一个时间段不能无限放号,所以需要一个配置表记录各个时间段的容量,在预约时做并发控制。
并发控制这个点很有意思。如果两个用户同时预约最后一个名额,都通过了数量校验,那就可能超售。面试的时候如果被问到“你的预约系统怎么防止超售”,一定要能答出来。最简单可靠的方案是用数据库的行级锁,在更新库存时使用SELECT ... FOR UPDATE,或者用一个乐观锁版本号字段,更新时校验版本号。这种并发问题在玩具项目里通常不会暴露,但在生产环境里十有八九会遇到,提前在项目里用上乐观锁,绝对是个亮点。
2.3 MyBatis的XML映射与动态SQL
这个项目用了MyBatis作为持久层框架,我强烈建议大家重点看一下XML里的动态SQL写法,因为这是MyBatis的灵魂。
图书检索这个功能就很典型。用户可能根据书名模糊查询,可能只看某个分类,可能筛选“仅看可借”的,可能按出版年份范围筛选。如果每个条件都写一个SQL,组合起来就是几十种情况,根本维护不了。用MyBatis的动态SQL,可以这么写:
<select id="queryBookList" resultType="map"> SELECT b.id, b.title, b.author, b.isbn, b.category_id, c.name AS category_name, b.stock, b.borrowed_count FROM book b LEFT JOIN category c ON b.category_id = c.id <where> <if test="keyword != null and keyword != ''"> AND (b.title LIKE CONCAT('%', #{keyword}, '%') OR b.author LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="categoryId != null"> AND b.category_id = #{categoryId} </if> <if test="onlyAvailable != null and onlyAvailable == true"> AND b.stock > b.borrowed_count </if> </where> ORDER BY b.create_time DESC </select><where>标签会帮我们智能处理AND前缀,避免拼接出错。这种写法在只有两三个筛选条件时优势还不明显,条件一多,好处立刻体现出来。新手如果只是简单用用注解开发,真的会错过MyBatis最实用的部分。
还有结果映射。数据库表字段是下划线风格,比如create_time、category_id,Java实体类字段是驼峰风格,比如createTime、categoryId。有两种处理方式:一种是在XML里用resultMap手动映射;另一种是在mybatis的配置文件里开启map-underscore-to-camel-case自动驼峰转换。这套源码里用了后者,配置一个属性就解决了,少写很多resultMap,效率更高,但也要求数据库命名必须规范,不然映射会出问题。
3. 从零到一:本地环境配置与项目运行
3.1 前端环境准备:Node.js与依赖安装
项目拿到手之后,我建议先配前端,因为前端启动相对独立,不需要后端的配合也能先看到页面长什么样。
前端用的是Vue,通常是Vue 2或者Vue 3,这个项目用的是Vue 2生态为主,配套的组件库是Element UI。先确认本机已经安装Node.js,版本不要太老,建议12以上。命令行运行node -v和npm -v看有没有输出版本号。
然后进入前端目录,运行npm install安装依赖。这一步经常会被卡住,大概率是npm默认的registry在国外,下载速度感人。解决方案是在项目根目录下新建一个.npmrc文件,写入国内的镜像源地址:
registry=https://registry.npmmirror.com再重新执行npm install,速度会明显改善。依赖安装完之后,执行npm run serve,Vue的开发服务器会启动在8080端口,浏览器打开http://localhost:8080就能看到页面了。
这里有一个初学者容易踩的坑:npm install的时候不要随便Ctrl+C中断,否则node_modules目录会处于半安装状态,之后各种奇怪的报错都会冒出来。如果真的卡死或者装坏了,最简单的办法是删掉node_modules目录和package-lock.json文件,重新执行npm install,比在网上搜一堆乱七八糟的修复方案更快。
3.2 后端环境准备:JDK、Maven与IDEA导入
后端环境相对麻烦一点,需要JDK、Maven和MySQL三样东西都准备好。
JDK版本要和项目保持一致。这个项目基于Spring Boot 2.x,一般用JDK 8或者JDK 11就行。我建议装JDK 8,兼容性最稳,配置环境变量时会多花两分钟,但项目启动时基本不会因为版本问题报错。
Maven也有一个镜像问题。默认从中央仓库下载依赖,国内网络环境同样很慢。需要在Maven的settings.xml里配置阿里云镜像。配置好之后,用IDEA打开后端工程,IDEA会自动识别Maven项目,开始下载依赖。首次下载可能需要十几分钟,耐心等它下载完。
有一个常见的坑是IDEA右下角显示依赖下载失败,通常是因为某个jar包下载不完整或者版本冲突。可以找到本地的Maven仓库目录,把对应的lastUpdated后缀文件删掉,然后重新刷新Maven,大多数情况下就能解决。
Maven配置里最容易被忽略的是Java编译版本。pom.xml里要确认maven-compiler-plugin的source和target设置的是1.8,不然后端代码里用了Java 8的语法,编译阶段可能会报错。
3.3 数据库初始化与核心配置项
数据库是整套系统的地基。拿到源码后,目录里一般会有一个sql文件夹,里面放着建库建表的脚本文件,有的还会带一些初始化数据。先打开MySQL,执行脚本文件:
mysql -u root -p < booksystem.sql或者直接在Navicat里打开SQL文件执行也行。执行成功后,会看到几个表已经建好,类别表里可能有预设的几个分类,图书表里可能有十几条测试数据,管理员的账号一般就是admin,密码通常是admin123或者123456。
后端要连接数据库,配置在application.yml或application.properties里。核心配置包括数据源地址、用户名、密码,以及MyBatis的mapper文件路径、日志级别等。一个典型的配置长这样:
server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: trueserverTimezone这一项特别值得注意。MySQL 8的驱动对时区检查很严格,如果不加serverTimezone,启动后访问数据库就会抛异常。改成Asia/Shanghai之后问题直接消失。
配置好之后启动Application主类,看到Spring Boot的启动日志里出现“Started Application in xxx seconds”,说明后端已经起来了。如果不放心,浏览器访问一下后端的某个健康检查接口,能返回JSON数据就说明一切正常。
4. 实操过程:数据库导入、联调与功能验证
4.1 数据库导入与验证数据是否就位
这个环节我从实际执行的角度走一遍。
先确保本机的MySQL服务已经启动。Windows下可以在服务管理器里看MySQL服务状态,macOS下用brew services list查看。启动OK之后,用命令行连接MySQL。如果你没有命令行操作的习惯,直接用可视化工具也可以。
执行SQL脚本时,建议先看清楚脚本的开头有没有CREATE DATABASE语句。有的脚本会自带建库语句,有的默认你已经在某个库里执行,后者需要自己先创建库。如果脚本执行报错,多半是字符集问题,在脚本开头加上SET NAMES utf8mb4;再执行一次就行。
导入完成之后,打开表看看数据。重点检查管理员账号是否存在、图书数据是否完整。有些版本的表数据是空的,需要自己先通过后台页面添加图书分类和图书信息。这一步如果没做好,前台登录进去看到的是空列表,会误以为系统出了bug,其实只是初始数据没导入。
4.2 后端连不上数据库的排查思路
在联调过程中,后端连不上数据库是本项目最高频的报错。核心报错信息通常是“Cannot create PoolableConnectionFactory”或者“Access denied for user”。
看到这类信息,先不要慌,按顺序排查。第一步,确认MySQL服务有没有启动;第二步,确认用户名的密码是不是正确;第三步,确认URL里写的数据库名和实际的库名一致;第四步,确认URL里的localhost没有拼写错,编码参数不要漏。
还有一个很常见的问题:MySQL 8之后,驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver,如果你的项目里pom.xml引用的mysql驱动版本是8.x,而代码里还写着旧驱动类名,就会启动报错。把类名改过来,或者干脆升级驱动版本,问题就解决了。
我实际测试中发现还有一种隐蔽的情况:本地装了多个MySQL版本,端口被占用或者服务没起来,导致连接的是空端口。用netstat -ano | findstr 3306查看一下端口监听状态,能省很多排查时间。
4.3 前后端联调时怎么处理跨域与Token
后端启动在8081端口,前端开发服务器在8080端口,两个端口不一致,浏览器就会触发跨域问题。前端调用后端接口时控制台会报"CORS policy"相关的错误。
解决办法有三个层面。第一个是后端开启跨域支持,代码里写一个CorsConfig配置类,注册CorsFilter,允许所有来源和常见请求头。第二个是前端配置Vue CLI的proxy代理,在vue.config.js里把请求代理到后端地址。第三个是生产环境用Nginx统一反向代理,前端和后端都在同一域名下,自然没有跨域问题。
个人建议本地开发时用proxy方案,因为它在浏览器层面看来请求是同源的,不需要后端额外开放跨域权限,更安全,也更贴近真实部署形式。在vue.config.js里大致这样的写法:
module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } } };前端项目的axios请求路径里如果写的是/api/login,请求发出后会被代理转发到http://localhost:8081/api/login,完美解决端口不同的问题。
Token问题也是联调阶段的经典难点。后端拦截器会拦截需要认证的接口,所有没有携带Token的请求都会被拒绝并返回401。如果前端登录成功后页面还是反复跳转到登录页,就要检查登录成功后的处理逻辑。重点看两个地方:第一个是登录成功后Token有没有正确存储,第二个是Axios拦截器里有没有把Token放进请求头。
// 请求拦截器 service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = token; } return config; });注意这里的Authorization头到底要不要加“Bearer ”前缀,取决于后端是怎么解析的。项目不同写法不同,打开后端过滤器看一眼没有成本,比瞎猜快得多。
4.4 功能验证:从普通用户到管理员的完整流程
联调成功后,我习惯把核心链路完整走一遍。先注册一个普通用户,登录系统,在图书列表里找一本书发起预约,然后退出登录,用管理员账号登录后台,在预约管理里确认这个预约,再模拟到馆取书核销。接下来去借阅记录里确认记录已经生成,最后还书,看库存数量有没有变回去。
走完这个闭环,可以说这个项目的核心功能已经全部跑通了。千万别觉得麻烦,这条链路里的任何一个环节报错,都意味着某个关联表之间的关系没有理解到位。把这条链路跑通,你对整个系统的理解会上一个台阶。
5. 常见问题与排查技巧实录
5.1 前端白屏,打开只看到空页面
这种问题十有八九不是代码逻辑问题,而是JS报错导致Vue没有挂载成功。按F12打开浏览器控制台,红色的报错信息会直接定位问题。
常见的原因有几种。第一种是路由模式问题,如果项目用了history模式,而开发服务器没有做相应的回退配置,刷新页面就会404,反复刷新有时候还会白屏。解决办法是在vue.config.js里配置historyApiFallback: true,或者后端做一个通配路由重定向到首页。第二种是依赖没装完整,某个npm包版本不兼容导致运行时报错。优先检查package.json里有没有安装vue-router和vuex,没有安装补上就能解决。第三种是最隐蔽的:API路径配置错误,导致页面加载时请求接口报403或404,前端代码里catch异常后没有兜底页面,看起来就是一片空白。
5.2 前端请求接口成功,但页面数据不刷新
这是前后端分离项目里容易忽视的一个问题。很多人误以为调了接口成功拿到数据后页面就会自动更新,其实还要看数据是不是响应式的。
Vue 2里的data对象,只有在预设好的属性被重新赋值时,页面才会响应更新。如果接口返回的数据直接赋值给一个对象的新增属性,大概率页面不会刷新,因为那个属性从一开始就不存在于data里。需要提前在data里把相关字段声明好,或者用this.$set()方法动态添加响应式属性。这个问题排查起来特别坑,因为控制台能看到数据有值,页面却是旧的。
5.3 后端日志乱码,或者数据库插入中文报错
中文乱码跑不掉两个原因。数据库连接URL里没有设置characterEncoding=utf8,后端接收到的请求没有正确设置编码过滤。
后端Spring Boot解决请求乱码最简单的方式是在application.yml里配置Http编码:
server: servlet: encoding: charset: UTF-8 enabled: true force: true数据库表默认字符集也要确保是utf8mb4。如果建表的时候没注意,表被建成了latin1,后面插入中文就会报"Incorrect string value"的错误。趁还没多少数据,把表的字符集改过来:
ALTER TABLE book CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;5.4 部署线上时前端的接口地址写死成localhost
本地联调通过之后,如果准备打包部署,最容易翻车的地方就是前端代码里残留的localhost地址。打包后的前端静态文件Nginx加载后是在服务器上跑的,它会向本机地址发请求,显然不通。
正确的做法是把前端请求路径做成环境变量或者统一配置,打包时根据环境动态替换。简单一点,也可以在全局常量文件里维护一个BASE_URL,改一处所有请求都变了。但最推荐的做法,还是走Nginx反向代理。把所有前端请求统一代理到后端的地址,Nginx配置大致这样:
server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样前端打包后的文件不用动,直接扔到Nginx目录,所有请求都走/api前缀,Nginx再把它们转发给Java后端进程。线上环境是今天部署,还是凌晨上班后再部署,以及回滚方案怎么做,这些都是数据库和代码之外的运维经验。
后端打包也有一点讲究。Spring Boot项目用Maven打包,执行:
mvn clean package -DskipTests打包出来的是一个可执行的Jar包。新手经常分不清target目录下两个名字不一样的Jar,一个是普通Jar,一个是Spring Boot的可执行Jar。可执行Jar的文件名后面一般带着-exec.jar或者用.jar.original区分。部署时只需要把可执行Jar扔到服务器上运行即可。如果直接打开普通Jar,大概率报“没有主清单属性”的错误。最简单的判断方式是用压缩软件打开Jar,看META-INF/MANIFEST.MF文件里有没有Main-Class配置。
6. 这个项目对学习和面试的价值点复盘
6.1 代码层面值得重点研读的三个文件
第一是后端的登录认证过滤器或拦截器。看明白Token怎么签发、怎么校验、怎么放行,前后端分离项目的鉴权秘密就基本打开了。第二是配置类里的跨域配置和MyBatis的XML文件,这里的写法在企业项目里几乎天天用。第三是Vue端路由守卫和Axios拦截器,这两个地方是前端工程里理解“登录态管理”的捷径。
很多同学看完课程视频,数据库和接口都会写,但一到鉴权就歇菜。这个项目的代码把这条链路完整地串起来了,认真读一遍,比刷十节课都管用。
6.2 面试时可以把哪些设计讲出亮点
面试官问“你这个项目有什么难点”的时候,照着下面几个点讲,比背八股文实在得多。
第一个讲预约防超售。为什么普通的库存判断会失效,怎么用乐观锁版本号解决,这是每个人都有机会遇到的真实并发问题。第二个讲前后端分离下的跨域方案选型,为什么开发环境用proxy,生产环境用Nginx反向代理,两者本质区别在哪。第三个讲Token失效和续期方案,如果不处理Token过期,用户在操作中途突然被踢出系统,体验很差;项目里可以选择每次请求时刷新Token有效期,也可以让前端在收到401时自动跳转登录页,更精细一点还能在Token过期前主动静默续期。
第四个可以讲数据库设计里为什么把图书和库存分开处理,如果你在表设计时真的有这个考量,在面试里能把字段之间的逻辑关系理清楚,技术深度立刻就不一样。
6.3 拿到一套源码之后应该怎么学
很多人拿到源码第一反应是跑起来再说,这没错,但跑起来只是第一步。我建议按这个顺序去读代码。第一步,先看数据库表结构,把每张表的字段含义和表间关系理清楚,可以用纸画一下。第二步,看后端的Controller,把接口清单和业务模块对应起来。第三步,重点看Service层,搞清楚一个业务请求从Controller到Service到Mapper再到数据库的完整调用链。第四步,回到前端,看Vue页面里的接口调用和路由配置,把前后端的对应关系串起来。
最后一步最容易被忽略,但恰恰是最有价值的:自己动手改一个小功能,比如给图书列表加一个“按出版社筛选”,或者加一个简单的数据导出功能。自己动手之后,才算真正把别人的代码消化成自己的东西。
我在实际把玩这套源码的过程中,最大的体会是:技术栈本身都不算新,但把前后端分离、JWT鉴权、MyBatis动态SQL、预约限流这些基础能力正确组合起来,本身就已经覆盖了大部分Java初级开发岗位的日常所需。如果你现在正缺一个能拿得出手的全栈项目,把这个项目从数据库设计到前端页面完整梳理一遍,你收获的绝对不止是“会运行”三个字。