又到一年毕业设计季,图书馆管理系统这个题目估计是Java方向最不缺人做的经典款了。它的业务足够清晰,CRUD一遍就能串起来,但又涵盖了登录认证、角色权限、借阅状态流转这些企业级开发躲不开的核心点,用Spring Boot做这套系统,基本等于把Java后端的主流套路完整走了一遍。这篇文章我就以我自己带项目、也帮人改过不少毕业设计的经验,从题目拆解、数据库设计、核心代码到部署演示和答辩被高频提问的点,全部捋一遍。无论你是拿来当毕设,还是想借这个项目把Spring Boot真正吃透,这份内容应该都能让你少走不少弯路。
1. 项目定位与整体设计思路
1.1 图书馆管理系统为什么是Java选题的常青树
图书馆管理系统能在毕业设计里长盛不衰,核心原因是它的业务模型极其贴合Spring Boot的能力边界。一张图书表、一张用户表、一张借阅记录表,就能把“增删改查”玩出花来,但又不只是机械的CRUD——借书要校验有没有库存,还书要计算是否超期,管理员和普通学生的权限还不一样。这些业务规则刚好能体现你对Service层逻辑的设计能力,对答辩来说是非常好的素材。
更重要的是,这个系统的数据关系属于“少而精”:用户与借阅记录是一对多,图书与借阅记录也是一对多,图书和分类是多对一。这种关系用外键或者逻辑关联都能清晰表达,既不会简单到让老师觉得没难度,又不会复杂到让你在毕设期间把自己逼疯。说实话,我见过太多人选了“电商秒杀系统”“高并发抢票系统”这种题目,最后答辩被老师一句“你这个并发到底怎么实现的”问到沉默。图书馆管理系统就不会有这种风险,它能让你的注意力集中在“把功能做完整、把逻辑写严谨”上。
从技术展示角度,这套系统也足够体面。Spring Boot自动装配让你能以最少的配置启动项目,Spring MVC负责请求分发,MyBatis-Plus帮你省掉大量SQL模板代码,Spring Security或者JWT做登录鉴权,再加上Vue的前端页面,整个就是一个标准的“前后端分离企业级项目”骨架。老师想看的技术点,它全都能覆盖到。
1.2 技术选型背后的真实考量
技术栈的选择不能只看“哪个火”,要看哪个能让你在两个月内顺利写完并讲清楚。
后端框架:Spring Boot 2.7.x。这里我建议不是特别追求新的话,就不要选Spring Boot 3.x。诚然3.x已经发布了很久,但3.x强制要求JDK17及以上,而很多学校的课程和本地环境还停留在JDK8上,且网上能找到的教程、踩坑帖绝大多数都基于2.x。2.7.x属于Spring Boot 2代的最后一个稳定大版本,既兼容JDK8,又包含了相对现代的机制,遇到问题搜一下基本都有答案。JDK选8或11都行,我更推荐8,因为Java环境变量的配置资料最多,老司机带路不容易翻车。
持久层:MyBatis-Plus而不是MyBatis。如果你只学过原生MyBatis,用MP也不是背叛,它只是帮你把单表CRUD和分页这种重复劳动封装掉了。毕业设计的时间普遍紧张,手写一大堆XMLMapper非常消耗精力,而且单表操作的SQL写多了毫无成长。用MyBatis-Plus的BaseMapper和LambdaQueryWrapper,图书列表、条件查询、分页这些东西基本五分钟就能搞定,你省下来的时间可以花在借阅业务逻辑这种更有含金量的地方。要注意的是,MP的分页需要配置一个PaginationInnerInterceptor,这属于不看源码就不知道的坑,后面我会讲。
前端方案:Vue 2 + Element UI。如果你前后端分离,这是最稳的组合。Vue 2的生态极其成熟,Element UI的表格、表单、弹窗组件几乎就是为管理后台量身定制的。如果你完全不会Vue,也可以退一步用Thymeleaf做服务端渲染,一个模板引擎就能搞定所有页面,但对展示Spring Boot的“前后端分离”能力来说会弱一些。我的建议是:有JS基础就上Vue,一点不会就用Thymeleaf兜底,别在毕设阶段把时间耗在学前端框架上。
数据库:MySQL 8.0。性能、稳定性、资料量都没得说,唯一要注意的是连接串里必须配serverTimezone=Asia/Shanghai,否则会有八小时时差问题。这个问题我在后面排查章节会详细说,现在先记住结论。
1.3 模块划分与整体架构
按照常见的功能需求,系统可以拆成几个清晰的功能域:用户认证模块(登录、注册、退出)、图书管理模块(图书录入、编辑、下架、分类管理)、借阅管理模块(借书、还书、续借、超期计算)、统计展示模块(图书总量、借阅排行)。每个模块内部都严格分层:Controller接收参数并做参数校验,Service处理业务逻辑,Mapper负责数据库交互,实体类(Entity)对应数据表,DTO/VO用于前端数据传递。
这里有个很多学生会忽略的点:不要把VO和Entity混着用。比如你查询借阅记录的时候,前端需要显示用户名和书名,但borrow_record表里只有user_id和book_id。如果你直接把Entity层的数据返回给前端,还得靠前端自己发多个请求去拼数据,既慢又乱。正确的做法是在Service里组装一个BorrowRecordVO,把联表查出来的信息一次性返回。这可能就是答辩时老师问“你这里怎么做的”时,你比别的学生多讲出来的亮点。
整个请求链路是:浏览器发起请求 → Spring MVC的DispatcherServlet分发到对应Controller → Controller调用Service接口 → Service实现类处理业务逻辑并调用Mapper → Mapper操作数据库 → 结果逐层返回。理解这条链路,你调Bug的时候就知道从哪一层下手。
2. 数据库设计与核心模型
数据库设计是整个系统最值得花时间打磨的部分,它直接决定了你后续写的代码是顺滑还是拧巴。图书馆管理系统的核心表就四张:用户表(sys_user)、图书分类表(book_category)、图书表(book)、借阅记录表(borrow_record)。下面逐一拆解字段设计的思路。
2.1 四张核心表的字段设计
用户表建议用sys_user命名而不是user,因为user在MySQL里是保留字,直接当表名会出现语法歧义,虽然加反引号能解决,但何必给自己挖坑。核心字段就是id、username、password、real_name、role、status。role我建议用字符串存,比如ADMIN和STUDENT,比用数字可读性好太多,判断逻辑也简单,没必要在这个阶段做复杂的RBAC权限模型。password字段必须存BCrypt加密后的哈希串,绝不能存明文。status用0和1表示禁用和正常,方便管理员锁号。
图书分类表很简单,id、category_name、description三件套。图书表稍微复杂一点,id、book_name、author、publisher、isbn、category_id、total_stock、available_stock、cover_url、status、create_time。重点说说库存字段:total_stock是总馆藏量,available_stock是当前可借数量。借书成功时available_stock减1,还书时加1。很多人设计的时候只留一个库存字段,然后通过统计借阅记录来算可借数量,这也能实现,但每次借书都要count一下未归还记录,性能差不说,代码也绕。直接用冗余字段维护可借数,是这个体量系统里最简单高效的做法。cover_url用于存放图书封面的访问地址,后面集成MinIO的时候会用到。
借阅记录表是系统的核心表,字段包括id、user_id、book_id、borrow_date、due_date、return_date、status、fine_amount。due_date是应还日期,一般借书日期加30天,你也可以自己定义借期规则。status是整个表的灵魂,我用四个值表示:0表示借出中,1表示已归还,2表示已续借,3表示已逾期。表设计好后,借阅业务逻辑其实就是这个状态字段的流转控制。
2.2 借阅状态流转与业务规则设计
借阅状态的流转是整个系统最需要讲清楚的地方,也是答辩时老师喜欢追问的部分。我按实际流程画个逻辑闭环:用户在“图书详情”页点击借书 → 系统校验用户状态正常、图书可借数量大于0、该用户没有未归还的同图书记录 → 创建一条borrow_record,status设为0,写入borrow_date和due_date → 同时把图书表的available_stock减1。
还书时:根据借阅记录id,把return_date设为当前时间,计算借阅天数是否超过due_date,如果超期则按超期天数乘以每日罚款金额(比如0.5元/天)计算fine_amount,status改为1,同时把图书表的available_stock加1。注意,还书这个操作必须在一个@Transactional事务里完成,否则会出现“记录改了但库存没加回来”这类数据不一致问题。
关于续借业务,如果你时间充裕可以加。续借的本质是把due_date向后延长30天,但有两个硬性条件:当前未逾期、只能续借一次。实现上可以在借阅记录表加一个renew_count字段,续借时判断若大于0则拒绝。
这里要特别提醒一点:不要把超期费用累积到数据库里反复计算。每次还书时根据最新的return_date现场计算,只把结果存进fine_amount字段。这样规则改动(比如从0.5元/天改成0.2元/天)不影响历史数据,逻辑也直观。
2.3 索引、约束与初始化数据
索引设计不用太复杂,但要有。主键id默认自增索引,这个不用管。需要额外加的是借阅记录表的user_id和book_id上的普通索引,因为你的高频查询都是“某个用户的借阅记录”“某本书的借阅记录”,没索引的话数据量大了会全表扫描。borrow_record表建议再加一个(user_id, status)复合索引,用来快速查“某用户当前未归还的记录”,这正好对应借书时的重复校验。图书表的category_id上加个普通索引即可,图书名用LIKE模糊查询,你说它走索引都不一定,所以也不用纠结。
外键我不建议物理创建。你可以建逻辑外键,但不要真的加FOREIGN KEY约束,否则做删除和关联查询时会有各种麻烦,这是业界现在普遍的做法,并不业余。
初始化数据一定要精心准备。系统至少需要一个管理员账号,用户名admin,密码用BCrypt加密后的值,比如123456对应的哈希串。图书数据不要只准备一两本,至少二十本,且要覆盖不同的分类,方便演示分页和条件查询。可以放几本经典的《Java编程思想》《算法导论》《深入理解Java虚拟机》《计算机网络》这类耳熟能详的书,答辩演示时老师看到会更有亲切感。
3. 核心功能实现与代码要点
3.1 登录认证与JWT无状态鉴权
登录认证是这个系统最值得讲清楚的技术点,也是面试和答辩的高频考点。传统的Session方案在前后端分离场景下不太好用,因为跨域请求处理SessionId比较麻烦,所以我直接用JWT做无状态鉴权。思路是:用户登录成功后,后端生成一个包含用户id和角色信息的Token返回给前端,前端存在localStorage里,之后每次请求在HTTP头的Authorization字段带上这个Token,后端通过拦截器解析Token并放行。
JWT工具类的生成和解析代码大致是这个样子:
public class JwtUtil { private static final String SECRET_KEY = "your-256-bit-secret-key"; private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L; // 7天 public static String generateToken(Long userId, String role) { Date now = new Date(); Date expireDate = new Date(now.getTime() + EXPIRE_TIME); return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }这里SECRET_KEY一定要足够长,至少256位,否则用HS256算法会报弱密钥错误。实际项目里密钥应该放在配置文件中,而不是写死在代码里,毕设的话也建议至少提到这个意识。
3.2 图书分页检索与条件查询
图书列表页是系统使用频率最高的功能,所以查询接口要支持分页、按书名模糊搜索、按分类筛选。用MyBatis-Plus实现这个非常快,核心代码如下:
public PageResult<BookVO> pageBooks(BookQueryDTO query) { Page<Book> page = new Page<>(query.getPageNum(), query.getPageSize()); LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(query.getBookName()), Book::getBookName, query.getBookName()) .eq(query.getCategoryId() != null, Book::getCategoryId, query.getCategoryId()) .orderByDesc(Book::getCreateTime); Page<Book> result = bookMapper.selectPage(page, wrapper); // 转VO返回 }注意like前面加了StringUtils.hasText判断,搜索关键词为空时就不拼接这条条件。这里千万不能用字符串拼接方式写SQL,否则会有SQL注入风险,这也是答辩老师很爱问的点,用MyBatis-Plus的Wrapper刚好能规避这个问题。模糊搜索的另一个小坑是LIKE '%关键词%'不会走索引,数据量不大时无所谓,但你要能说出这个原理,会显得你考虑过性能问题。
3.3 借阅与归还业务逻辑
这是系统的业务核心,也是我建议你亲手逐行写清楚的部分。借书逻辑的实现思路是校验、落库、改库存三步,用@Transactional保证要么全部成功,要么全部回滚:
@Transactional(rollbackFor = Exception.class) public void borrowBook(BorrowRequestDTO dto) { User user = userMapper.selectById(dto.getUserId()); Book book = bookMapper.selectById(dto.getBookId()); if (user == null || !user.getStatus().equals(1)) { throw new BusinessException("用户不存在或已被禁用"); } if (book == null || book.getAvailableStock() <= 0) { throw new BusinessException("图书不存在或无库存"); } Long count = borrowRecordMapper.selectCount(new LambdaQueryWrapper<BorrowRecord>() .eq(BorrowRecord::getUserId, dto.getUserId()) .eq(BorrowRecord::getBookId, dto.getBookId()) .in(BorrowRecord::getStatus, 0, 2, 3)); if (count > 0) { throw new BusinessException("你已借阅该图书且未归还"); } BorrowRecord record = new BorrowRecord(); record.setUserId(dto.getUserId()); record.setBookId(dto.getBookId()); record.setBorrowDate(new Date()); record.setDueDate(DateUtil.offsetDay(new Date(), 30)); record.setStatus(0); borrowRecordMapper.insert(record); book.setAvailableStock(book.getAvailableStock() - 1); bookMapper.updateById(book); }还书时就要计算超期费用了。我的规则是:超期天数乘以0.5元每天,不足一天按一天算。计算逻辑:
@Transactional(rollbackFor = Exception.class) public void returnBook(Long borrowRecordId) { BorrowRecord record = borrowRecordMapper.selectById(borrowRecordId); if (record == null || record.getStatus() == 1) { throw new BusinessException("借阅记录不存在或已归还"); } Date now = new Date(); record.setReturnDate(now); record.setStatus(1); if (now.after(record.getDueDate())) { long daysLate = (now.getTime() - record.getDueDate().getTime()) / (1000 * 60 * 60 * 24); if ((now.getTime() - record.getDueDate().getTime()) % (1000 * 60 * 60 * 24) != 0) { daysLate += 1; // 不足一天按一天算 } record.setFineAmount(BigDecimal.valueOf(daysLate).multiply(new BigDecimal("0.5"))); } else { record.setFineAmount(BigDecimal.ZERO); } borrowRecordMapper.updateById(record); Book book = bookMapper.selectById(record.getBookId()); book.setAvailableStock(book.getAvailableStock() + 1); bookMapper.updateById(book); }金额字段建议用BigDecimal而不是double,double的浮点精度问题在涉及钱时是大忌,你就跟老师说用BigDecimal是为了保证金额计算精确,这又是一个加分点。
3.4 图书封面上传与MinIO集成
图书封面上传是个容易被忽略但很出彩的功能。最简单的实现是上传到本地磁盘,然后把本地路径存到数据库,但这样会碰到两个问题:第一,前后端分离部署时,前端访问不到后端磁盘的静态文件;第二,本地存储无法扩容,功能太简陋。建议集成MinIO,它是开源的轻量级对象存储服务,单机部署非常容易,而且社区资料很多。
先在pom.xml引入依赖:
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.7</version> </dependency>然后在配置文件里加MinIO的连接信息,写一个MinioService封装上传、下载、删除操作。上传的核心代码:
public String uploadFile(MultipartFile file) throws Exception { String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename(); minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return "http://" + endpoint + "/" + bucketName + "/" + fileName; }上传成功后返回的URL直接存到图书表的cover_url字段,前端用<img>标签就能展示。集成MinIO这个点虽然做起来不难,但在毕业设计里绝对算一个亮点。工程量小、技术点新、可解释性强,非常推荐做进去。
4. 项目部署、配置与演示准备
4.1 环境搭建与核心配置文件
环境搭建这里重点提几个容易踩坑的地方。如果你还没装JDK,先装JDK8,安装时记住路径,然后在系统变量里新建JAVA_HOME指向JDK目录,再把%JAVA_HOME%\bin追加到Path变量里。装完在命令行敲java -version能输出版本号就算成功。Maven和IDEA的安装我就不啰嗦了,网上的教程一搜一大把。
Spring Boot的配置文件application.yml里最核心的配置有三个地方。第一是端口,默认8080,用server.port改。第二是数据源,这里最容易错:
spring: datasource: url: jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone=Asia/Shanghai是必带的,否则你往数据库存时间时会发现时间对不上。useSSL=false和allowPublicKeyRetrieval=true是MySQL 8.0连接时的常见要求,一个是关闭证书验证,一个是允许密钥检索,不加可能直接连不上。第三是MyBatis-Plus的配置,把日志打印开一下方便调试:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl4.2 一键启动与演示数据准备
启动顺序是:先启动MySQL,然后用Navicat或命令行执行你的init.sql脚本建库建表、插入初始数据,最后用IDEA启动Spring Boot项目。IDEA里运行main方法就能启动,端口起没起来看控制台的Spring Boot Logo和Started Application in x seconds这行字。
这里要特别说一个演示的细节:演示数据一定要提前准备到恰到好处。不要只用系统默认插入的那二十本书,你可以手动在系统里多创建几个不同分类的图书,把学生账号也注册好,甚至在演示前故意留出一条未归还且已超期的借阅记录,这样演示还书功能时超期罚款的计算效果就能直接展示,而不是现场等30天。这类细节能让你的演示流畅度提升一个档次。
4.3 Vue前端打包后放进SpringBoot
如果你用了前后端分离,最后的部署环节有个很实用的技巧:把Vue项目打包后塞进SpringBoot的静态资源目录,打包成一个Jar运行。这样你在答辩时只需要启动一个Jar,就能同时提供后端接口和前端页面,不需要演示时现场启动两个服务,降低被环境问题卡住的概率。
具体操作是:Vue项目里把src/utils/request.js文件中的baseURL改成相对路径/api,这样所有网络请求都会打到同源地址上。然后执行npm run build,构建成功后会在dist目录下生成一堆静态文件,把这些文件全部复制到Spring Boot的src/main/resources/static目录下。启动后端后,浏览器访问http://localhost:8080就能直接打开前端页面。
这里有个大坑必须提醒:如果前端路由用的是history模式,F5刷新页面时会404,因为静态资源里并不存在那个路径对应的物理文件。解决办法有两个,一个是改用hash模式路由,另一个是在后端加一个Controller把非/api路径的请求转发到index.html。我建议毕设直接用hash模式,改一行代码的事,最省心。
5. 常见问题排查与答辩避坑指南
5.1 数据库时区与连接八小时问题
这两个问题是学生问过我最多的。时区问题的现象是系统时间和数据库时间差了8小时,原因是MySQL连接串里没加serverTimezone=Asia/Shanghai,加上就好。
八小时问题则是MySQL默认的wait_timeout是28800秒,也就是8小时,如果应用连续8小时没有访问数据库,连接会被服务端断开,客户端再用这个连接就会报Communications link failure。解决办法是在数据源配置里加连接池的保活和校验,用Druid或HikariCP配置一下即可:
spring: datasource: hikari: connection-timeout: 30000 validation-timeout: 3000 max-lifetime: 1800000 idle-timeout: 6000005.2 Maven依赖冲突与版本坑
用Spring Boot最舒服的地方就是spring-boot-starter-parent帮你锁定了大量依赖版本,但自己额外加依赖时还是容易出问题。最典型的两个报错是:启动时报Failed to configure a DataSource,这是因为pom.xml里引入了数据库相关starter但没配数据源,或者配置了数据源但没引入数据库驱动依赖。
另一个非常典型的坑是MyBatis-Plus和Spring Boot版本不兼容。如果你用了Spring Boot 3.x,就需要用MyBatis-Plus 3.5.5以上版本,否则启动时直接报错。用2.7.x加MyBatis-Plus 3.5.3组合是最稳的。香菇(Lombok)也要注意,Spring Boot 3.x配合的Lombok版本必须是1.18.30以上,否则@Slf4j和@Data注解会失效。
5.3 跨域、拦截器与OPTIONS预检请求
前后端分离开发时,前端服务跑在5173端口(Vite),后端跑在8080端口,不管你是用Axios还是fetch,跨域问题躲不掉。解决方式有全局CORS配置和拦截器处理两种,我推荐直接写一个配置类:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }这里有个隐蔽的问题:浏览器在跨域请求时,复杂请求会先发一个OPTIONS预检请求。如果你的登录拦截器把OPTIONS请求也拦截了,前端就会报跨域错误,但实际上是拦截器把预检请求挡掉了。解决办法是在拦截器里直接放行OPTIONS请求:
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; }这个坑如果你没遇到过,答辩时老师如果问起来,你能答出OPTIONS预检的原理,会是一个加分项。
5.4 答辩高频问题与回答思路
最后帮你们梳理一下答辩时老师最爱问的问题和比较稳妥的答法。
为什么选Spring Boot而不是SSH或SSM?答:Spring Boot的核心是自动装配和约定大于配置。它内置了Tomcat、自动绑定数据源和Bean,能将搭建项目的时间从几小时缩短到几分钟,同时生态完善,适合快速开发企业级应用。顺便可以提一句Spring Boot的自动装配原理——通过@EnableAutoConfiguration加载META-INF/spring.factories中的配置类,按条件注解@ConditionalOnClass、@ConditionalOnProperty决定哪些配置生效。这个知识点要提前背熟。
JWT和Session有什么区别?答:Session是服务端存储,需要占用服务端内存且存在Session共享问题;JWT是无状态的,用户信息加密存放在Token里,服务端通过验签即可信任,天然适合分布式和前后端分离的场景。缺点也很明显,Token一旦签发无法主动失效,所以实践中可以结合Redis做黑名单,不过我们系统现在的体量不需要。
数据库索引为什么用普通索引而不是唯一索引?答:唯一索引会限制业务灵活性。比如借阅记录表里同一本书可以被不同用户借,如果对book_id建唯一索引,整个借阅模型就崩了。索引不是越多越好,必须结合查询场景去设计。
事务你是怎么保证数据一致性的?答:在Service方法上加@Transactional,比如借书时创建借阅记录和扣减库存是两个操作,如果第二个操作失败,事务回滚,第一个操作也不会生效。同时我会在业务代码里做前置校验,把异常消灭在逻辑层,避免数据库报错后再回滚带来的开销。
这个系统的并发能支撑多少?别慌,这个问题就是想试探你有没有考虑过。你要答:当前架构针对的是中小型图书馆,单机并发足够;如果要提升性能,可以引入Redis做热点数据缓存,比如图书库存预扣减,再结合消息队列削峰,把写操作异步化。
这些问题的核心不是要你真的做过高并发,而是考察你有没有理解自己所写代码的边界和原理。提前把这些答法过几遍,答辩的时候姿态会稳很多。
我个人在实际答辩和帮学生改项目中体会最深的,就是别把系统做成功能堆砌。很多同学喜欢把Redis、MQ、ElasticSearch一股脑全整合进去,结果每一项都浅尝辄止。图书馆管理系统真正拿得出手的,是你把借书还书这个闭环做严谨了:状态流转清晰、超期计算精确、事务边界正确、接口返回合理。把Spring Boot的开发模式和MyBatis-Plus的用法讲明白,比堆十个中间件都管用。做完这套系统,你其实已经把Java后端开发的主线流程完整走了一遍,后面再去做任何管理系统,大体都是同一个套路。祝题主毕业答辩顺利,也祝真正想入行的人借这个项目叩开Java后端的大门。