news 2026/10/7 3:13:55

Spring Boot图书借阅系统开发实战:从骨架搭建到避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot图书借阅系统开发实战:从骨架搭建到避坑

简介:这套基于Spring Boot实现的图书借阅系统源码,面向正在做Java毕业设计或全栈课程设计的同学,也适合希望快速搭建完整业务系统的开发者。项目围绕图书借阅场景,覆盖学生管理、教师管理、登录权限过滤、图书借还查询等模块,后端采用Spring Boot与Servlet,前端使用JSP、HTML、CSS和JavaScript,并配有MySQL数据库脚本,技术栈完整,便于理解前后端交互与分层开发。压缩包共234个文件,大小约5.78MB,核心类型包括46个Java源文件、9个JSP页面、14个HTML页面、23个JavaScript文件、8个CSS样式、1个SQL数据库脚本及6个jar依赖包,另有75张GIF操作演示图,可直观对照页面流程。源码已经本地编译验证,按文档配置好环境即可运行,难度适中,内容经过助教审定,可作为毕业设计原型或全栈进阶的参考素材。目前已有125人浏览学习,有需要的同学可放心下载,遇到问题也可向博主咨询。

1. 为什么 Spring Boot 图书借阅系统是毕设钉子户:三张表撑起完整闭环

做 Java 后端和毕业设计这几年,我拆过不下二十个 Spring Boot 项目,图书借阅系统是出现频率最高的一个。原因很现实:它的业务闭环完整——管理员管书、学生借书、到期还书,三张表就能覆盖用户、权限、库存、状态流转四类问题,Spring Boot 又恰好把这套场景的默认配置都做好了。这篇笔记写给两类人:一是拿源码改毕设、想快速跑通并看懂每个模块写法的同学;二是刚接触 Spring Boot + MyBatis、想找个真正全栈项目练手的从业者。按项目结构、核心借阅逻辑、前后端联调、避坑、进阶的顺序拆,每一步都给能直接抄的配置和代码。

2. 读懂项目骨架:目录结构、配置文件与数据库脚本一次性讲透

拿到一个 zip 包,第一件事不是点运行,而是先看目录结构。很多同学把项目导入 IDEA 就急着启动,结果报了一堆错,其实一半的报错都能从配置文件和建表脚本里提前看出来。

2.1 前后端是否分离:先看目录结构再决定启动方式

这套源码通常有两种组织方式。第一种是纯后端工程,前端 Vue 项目单独放在frontend/目录;第二种是把 Vue 打包后的dist目录直接放进src/main/resources/static,做成一个可单独部署的 jar。两种方式的启动步骤差别很大,目录结构大致如下:

book-manage-system/ ├── src/main/java/com/example/library/ │ ├── controller/ # BookController, UserController, BorrowController │ ├── service/ # 业务逻辑层 │ ├── mapper/ # MyBatis 数据访问层接口 │ ├── entity/ # 数据库实体类 │ ├── config/ # 跨域、拦截器、定时任务配置 │ └── LibraryApplication.java ├── src/main/resources/ │ ├── application.yml # 端口、数据源、MyBatis 配置 │ ├── mapper/ # XML 文件(如果用的是 XML 方式) │ └── static/ # 有则说明是前后端一体 ├── frontend/ # 有则说明是前后端分离 ├── sql/ │ └── library.sql # 建表 + 初始化数据 └── pom.xml

拿到项目先确认两件事:src/main/resources/static下是否有打包好的前端文件,以及根目录是否有独立的frontend工程。如果只有前者,直接用 IDEA 运行LibraryApplication主类,访问http://localhost:8080就能看到登录页;如果存在frontend,需要先跑后端,再在frontend目录下执行npm run dev起前端,开发端口通常用 8081,靠跨域配置对接。

为什么这套骨架的数据访问层选 MyBatis?因为图书借阅的查询条件简单但变更频繁——管理员列表要按书名、作者、分类、ISBN 任意组合过滤,MyBatis 的动态 SQL 写这类多条件查询比 Spring Data JPA 的 Specification 直观得多。JPA 在实体关系复杂的系统里有优势,但这种三张表的场景 MyBatis 更省心,这也是大多数毕设源码选择它的原因。

2.2 application.yml 里的三个关键配置

Spring Boot 的配置基本围绕端口、数据源、框架开关三个维度展开。这个项目的application.yml核心内容通常长这样:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.library.entity

三个地方要改:端口默认 8080,本地被占用就换成 8081;数据源密码换成自己 MySQL 的密码;serverTimezone必须写Asia/Shanghai,否则新版 MySQL 驱动会在启动或首次查询时报警时区错误。type-aliases-package的作用是简化 XML 里的实体类引用,写resultType="Book"而不是一长串包路径。

几个核心配置项的取值和风险点,我列了一个自查表:

配置项我常用的值备注
server.port8080 或 8081端口冲突时先改这里
spring.datasource.urljdbc:mysql://localhost:3306/library_db时区和字符集参数必须带上
spring.datasource.password自己 MySQL 的密码zip 里默认 123456,不改必报错
mybatis.mapper-locationsclasspath:mapper/*.xmlXML 放错目录会抛 BindingException

顺带说一句 MyBatis 的两种写法。有些项目不用 XML,直接在 Mapper 接口上写@Select注解,代码更短,但 SQL 和 Java 耦合在一起,复杂查询时格式化很难看。毕设和中小项目我更推荐 XML 方式:SQL 独立管理、改完不用重新编译,排查问题时能直接打开 XML 看语句。前提是application.yml里的mapper-locations路径要和实际目录一致。

2.3 数据库脚本:核心表初始化和管理员账号

sql/library.sql是这个项目最值钱的部分。三张核心表的建表语句如下,字段设计基本覆盖了图书馆业务的主要场景:

-- 用户表 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT '密码(MD5或BCrypt)', `real_name` varchar(50) DEFAULT NULL COMMENT '姓名', `role` tinyint(1) NOT NULL DEFAULT '2' COMMENT '1=管理员 2=学生', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=正常 0=禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图书表 CREATE TABLE `book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `isbn` varchar(20) DEFAULT NULL, `title` varchar(200) NOT NULL COMMENT '书名', `author` varchar(100) DEFAULT NULL, `category` varchar(50) DEFAULT NULL COMMENT '分类', `total` int(11) NOT NULL DEFAULT '1' COMMENT '总库存', `available` int(11) NOT NULL DEFAULT '1' COMMENT '可借数量', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 借阅记录表 CREATE TABLE `borrow_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `book_id` int(11) NOT NULL, `borrow_time` datetime DEFAULT NULL COMMENT '借书时间', `due_time` datetime DEFAULT NULL COMMENT '应还时间', `return_time` datetime DEFAULT NULL COMMENT '实际归还时间', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0=在借 1=已还', `fine` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '滞纳金', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

user在 MySQL 里不算保留字,但为了保险,很多项目会写成t_user或加反引号。三张表的关系很直白:借阅记录通过user_id和book_id关联两边,真正的业务逻辑集中在borrow_record的状态字段和book.available的加减上。初始化数据里一般有一个管理员账号admin/admin123和一个学生账号student/123456,数据库导入后先拿管理员账号登录,验证角色权限是否正常。

有个细节值得注意:book.available被设计成单独字段,而不是每次借阅时去统计未归还记录数。原因是图书列表页需要高频展示可借数量,单独字段只需查一行,连表统计在数据量大时会影响接口响应。代价是每次借还都要手动维护这个字段,所以借阅和归还的 Service 方法必须保证事务一致性。borrow_record.fine字段同理,归还时算好滞纳金直接存进去,管理员统计超期费用时就不用每行实时计算。

3. 把借阅流程写清楚:从 Controller 到 Service,库存和状态怎么流转

图书借阅系统的业务核心不在增删改查,而在借阅和归还时的状态机控制。新手最容易在这里翻车:借书时只插了一条记录,忘了改库存;还书时只改了记录状态,忘了恢复库存,导致系统数据对不上。

3.1 借阅接口:先查状态、再扣库存、最后写记录

先看借阅的 Controller 入口:

@RestController @RequestMapping("/api/borrow") public class BorrowController { @Autowired private BorrowService borrowService; @PostMapping("/add") public Result borrow(@RequestBody BorrowDTO dto) { // dto 中只需要 userId 和 bookId 两个参数 return borrowService.borrow(dto.getUserId(), dto.getBookId()); } }

业务逻辑在 Service 层完成,拆成四步:校验用户状态、查询图书库存、扣减可借数量、插入借阅记录。核心代码如下:

@Transactional public Result borrow(Integer userId, Integer bookId) { // 1. 校验用户是否存在且状态正常 User user = userMapper.selectById(userId); if (user == null || user.getStatus() == 0) { return Result.error("用户不存在或已被禁用"); } // 2. 查询图书并校验库存 Book book = bookMapper.selectById(bookId); if (book == null || book.getAvailable() <= 0) { return Result.error("图书不存在或库存不足"); } // 3. 扣减可借数量,用受影响行数判断是否成功 int rows = bookMapper.decreaseAvailable(bookId); if (rows == 0) { return Result.error("该图书已被借完,请稍后再试"); } // 4. 生成借阅记录,默认借期 30 天 BorrowRecord record = new BorrowRecord(); record.setUserId(userId); record.setBookId(bookId); record.setBorrowTime(new Date()); record.setDueTime(DateUtils.addDays(new Date(), 30)); record.setStatus(0); borrowMapper.insert(record); return Result.success("借阅成功"); }

这里DateUtils是项目里封装的天数计算工具类,等价于new Date()加上 30 天的毫秒数。三个要点值得说清楚。

第一,@Transactional必须加上。第三步扣库存和第四步插记录必须同时成功或同时回滚,否则会出现「记录没插上但库存已经扣了」的数据不一致。这里要啰嗦一句:Spring Boot 2.x 默认开启 CGLIB 代理,@Transactional可以放心用在实现类内部,不需要额外配置。

第二,bookMapper.decreaseAvailable(bookId)的 SQL 要写成UPDATE book SET available = available - 1 WHERE id = ? AND available > 0,返回受影响行数为 0 就说明库存已经没了。这种写法能避免并发借阅时超借,比「先查询再判断再更新」要稳得多,不需要引入悲观锁。

第三,校验顺序有讲究。用户校验走的是主键索引,成本最低;图书库存是所有借书请求争抢的资源,把它放到后面,扣减时锁的持有时间就短。把最容易失败的库存校验放在真正修改之前,可以避免写库前做耗时操作。

3.2 归还接口:状态回写和超期费用计算

归还逻辑比借阅多一个点:超期费用计算。完整方法如下:

@Transactional public Result returnBook(Integer recordId) { // 1. 查询借阅记录,状态必须是在借 BorrowRecord record = borrowMapper.selectById(recordId); if (record == null || record.getStatus() != 0) { return Result.error("借阅记录不存在或已归还"); } // 2. 计算超期费用:每天 0.5 元,未超期为 0 Date now = new Date(); if (now.after(record.getDueTime())) { long days = (now.getTime() - record.getDueTime().getTime()) / (1000 * 3600 * 24); record.setFine(new BigDecimal(days).multiply(new BigDecimal("0.5"))); } else { record.setFine(BigDecimal.ZERO); } record.setReturnTime(now); record.setStatus(1); borrowMapper.updateById(record); // 3. 恢复图书库存 bookMapper.increaseAvailable(record.getBookId()); return Result.success("归还成功,滞纳金:" + record.getFine()); }

超期天数的(long) / (1000 * 3600 * 24)是毫秒转天数的标准写法,整数除法会丢掉小数部分,所以超期不满 24 小时不收费。默认规则是超过应还日期才开始计费,如果你想让当天归还也不收费,加一个if (days >= 1)的判断即可。

归还时最容易漏的一步是恢复库存。我见过不少改造版本只更新了borrow_record.status,忘了调bookMapper.increaseAvailable,结果书明明还了,列表里还是「不可借」。如果自己改代码,建议把「更新记录」和「恢复库存」两行紧挨着写,中间不要插入日志或通知操作。

归还流程走完后,borrow_record里就有了一条完整时间线:借书时间、应还时间、实际归还时间、滞纳金。管理员端的统计报表大多从这张表聚合,比如按月统计借阅趋势、按图书统计借阅次数。所以归还时把return_time和fine写对,后面做报表就不会返工。

3.3 登录认证与权限拦截:管理员和学生各走各的路

借阅接口不能对所有请求开放。这个项目用的是最基础的拦截器方案,而不是 Spring Security——对毕设和中小项目来说,拦截器逻辑直观、调试简单,没有 Security 那套过滤器链的复杂度。拦截器代码如下:

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { response.setStatus(401); return false; } // 管理员专属接口额外校验角色 String uri = request.getRequestURI(); if (uri.startsWith("/api/admin/") && user.getRole() != 1) { response.setStatus(403); return false; } return true; } }

拦截器注册到 WebMvcConfigurer:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register"); } }

两行配置决定了谁能访问什么:/api/user/login和/api/user/register放行,其余/api/**必须登录;管理员专属操作通过路径前缀/api/admin/加角色判断。我这里为了表达简洁写了new LoginInterceptor(),但如果这个拦截器里注入了 Mapper 或 Service 依赖,必须改成构造器注入或标注@Component后从容器拿实例,否则启动没问题、一到拦截就会空指针。

登录接口本身没什么特殊,登录成功后在session里放一个loginUser对象,后续拦截器读取。注意密码加密一致性:注册时用什么算法,登录时就必须用什么算法比对,这个坑放到第 5 章细说。

4. 全栈联调实战:Vue 页面怎么对接 Spring Boot 后端

很多同学代码能跑起来但页面调不通,问题基本集中在跨域、接口地址、静态资源路径三个地方。这一章把三条路都走一遍。

4.1 开发期跨域:三种方案选一种就行

前后端分离开发时,前端地址是http://localhost:8081,后端是http://localhost:8080,浏览器的同源策略会拦截跨域请求。常见三种解决方式:后端加@CrossOrigin注解、后端加全局 CORS 配置、前端配 Vite/Webpack 代理。我更推荐前端代理,因为不动后端代码,生产环境不受影响。

不过大多数毕设源码里默认用全局 CORS 配置,因为最省事、对后端侵入最小。写法如下:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .maxAge(3600); } }

两个注意点。第一,allowedOriginPatterns("*")不要漏掉,如果写成allowedOrigins("*"),在 Spring Boot 较新版本上启动会直接抛异常。第二,addMapping范围写成/api/**就足够,前端页面和后端同源部署时这套配置也不会造成影响。调试时如果浏览器控制台出现CORS error,这个配置加上去立刻消失。

为什么源码里往往是后端 CORS 而不是前端代理?因为大多数毕设开发时是单机环境,后端加一个配置就全组通用了;前端代理更适合多人协作时一套固定开发环境。单机写毕设选哪个都行,上线部署时 CORS 配置并不影响同源,留着不删完全没问题。

4.2 生产部署:把 Vue 打包放进 Spring Boot 的 static 目录

联调完成后,不可能一直开着两个端口给人演示。最简单的做法是前端构建,然后让 Spring Boot 托管静态资源:

# 在 frontend 目录下执行,生成 dist 文件夹 npm run build

把dist目录里的内容全部复制到src/main/resources/static/,重新打包运行后端,浏览器直接访问http://localhost:8080就能同时拿到前后端。注意dist/index.html里静态资源引用路径:如果打包后页面空白或控制台 404,先看index.html里script标签的src是/js/app.js还是./js/app.js,Spring Boot 托管场景下baseURL或assetsPublicPath配置为/通常最稳。

这里有一个经典坑:Vue Router 用 history 模式时,刷新/books或/admin页面会返回 404,因为后端没有对应路由。两个解决办法,一是后端加一个转发规则,二是 Vue 路由改用 hash 模式。

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{path:[^\\.]*}").forwardTo("/index.html"); } }

这段配置的作用是把所有不带文件后缀的路径转发到index.html,交给前端路由处理。注意边界:带.的路径(如.js、.css、.png)不会走这个规则,静态资源不会被错误转发。hash 模式则简单得多,URL 形如/#/books,不用动后端。我一般建议毕设项目直接用 hash 模式,少一个出错点。

4.3 Axios 请求封装:路径统一和响应拦截

Vue 页面里的请求不能每个组件都写一遍axios.get('http://localhost:8080/api/...'),那样后端地址一改就要全局替换。通常在src/utils/request.js里封装一个实例:

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 统一处理响应与错误 request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { window.location.href = '/#/login' } return Promise.reject(error) } ) export default request

baseURL写成/api而不是http://localhost:8080/api,有讲究:生产环境下前后端同源,相对路径天然可用;开发环境靠前端代理或 CORS 解决,不会有写死绝对地址带来的跨域隐患。响应拦截器统一抽出response.data,每个页面不需要重复写一层数据解包。这段代码虽然短,但没有它,后续所有接口联调都会消耗大量时间在路径拼写和错误处理上。

5. 避坑与排查:图书借阅系统最常见的 5 个翻车现场

联调跑通之后,我把这类项目里高频出现的问题整理了五条,每条按「现象 → 原因 → 解决」来写。

5.1 8080 端口占用,服务起不来

现象:启动时日志出现Port 8080 was already in use,控制台最后一行显示APPLICATION FAILED TO START,服务直接退出。

原因:本地有别的服务占用了 8080,可能是之前跑过一次没关掉的后端进程、Nginx,或另一个 IDEA 里的调试实例。

解决:先在命令行确认占用者。Windows 下执行netstat -ano | findstr :8080拿到 PID,再taskkill /F /PID <pid>;Linux/macOS 用lsof -i:8080 -P -n定位进程后 kill。我自己的习惯是直接把application.yml的端口改成 8081,并同步前端代理目标,这样不用猜是谁占用的。另外注意,从 zip 解压后可能之前已经在后台跑过一个实例,把多余的 Java 进程一并清掉再启动。

5.2 登录一直报「用户名或密码错误」

现象:前端登录接口返回错误,但数据库里查出的密码字段怎么看都是对的。

原因:注册时用的加密算法和登录时不一致,或者直接手动改了数据库里的密码,导致格式被破坏。看一眼存储格式就能判断:32 位十六进制字符串是 MD5;以$2a$10$开头的是 BCrypt。

解决:如果是 MD5,登录时用DigestUtils.md5Hex(rawPassword)加密后再比对;如果是 BCrypt,用BCryptPasswordEncoder.matches(rawPassword, dbPassword)。两种方式不能混用。手动改数据库密码时,也要用相同算法生成新值,别直接在 SQL 里写明文。我通常在登录 Service 入口打一行日志,把前端传过来的密码打出来,看一眼是明文还是已经加密的值,十秒定位问题。

5.3 借书成功后库存没减

现象:前端提示借阅成功,借阅记录也生成了,但图书列表里的可借数量没变化。

原因:Service 里「先 selectById 判断 available 再 update 减库存」,中间没有并发保护;或者扣减 SQL 只写了SET available = available - 1 WHERE id = ?,库存已经是 0 时还会继续减成负数。

解决:把扣库存 SQL 改成条件更新:UPDATE book SET available = available - 1 WHERE id = ? AND available > 0,再判断rows == 0。因为保存借阅记录和扣库存都在@Transactional事务里,条件更新会在事务内持有行锁,直到事务提交才释放。想验证的话,开两个浏览器同时点借书,两个请求只能有一个成功,另一个返回「库存不足」。

5.4 Vue 打包后刷新页面 404

现象:npm run dev开发时一切正常,打包放进 Spring Boot 后,在/books或/admin这样的页面刷新就 404。

原因:Vue Router 用 history 模式时,路由路径依赖前端 JS 渲染,浏览器刷新会把完整地址作为静态资源请求发给后端,Spring Boot 找不到对应的 Controller 或静态文件,自然返回 404。

解决:两个方案任选。后端加addViewControllers转发规则(见 4.2 的代码),把非静态资源路径转发给index.html;或者把 Vue Router 改成 hash 模式,在路由配置文件里把mode: 'history'改为mode: 'hash',URL 会变成/#/books,这种地址不需要后端配合。毕设项目我强烈建议直接上 hash,少一个后端配置就少一个故障点。

5.5 中文乱码:不是换个编码就能解决

现象:页面上中文正常,插入数据库后变成???;或者页面本身乱码,数据库却是对的。

原因:两件事要分两个层面看。数据库层面,连接 URL 没指定characterEncoding=utf8,或表结构本身是latin1编码;页面层面,IDEA 控制台显示编码和项目文件编码不一致,或前端页面漏了<meta charset="utf-8">。

解决:JDBC URL 里补上useUnicode=true&characterEncoding=utf8;建表时统一CHARSET=utf8mb4;已经建好的表执行ALTER TABLE book CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。页面乱码就检查 IDEA 的File Encodings,把全局编码、项目编码、控制台编码全部改成 UTF-8。改完重启项目,插入一条带中文的新数据验证,不要只盯着旧数据看。

以上五条覆盖了这类项目 80% 的问题。如果遇到别的报错,优先看控制台最底部的Caused by,那才是真正的错误原因,上面一长串at com.xxx...都是调用栈噪音。

6. 进阶实战:加超期未还提醒和接口冒烟测试

项目跑通以后不要急着交差,做两个很小的增强,能让整套代码的可信度上一个台阶。

第一个是超期未还自动提醒。用 Spring Boot 自带的定时任务就能实现,每天凌晨检查所有「在借」且超过应还时间的记录:

@Component public class BorrowTask { @Autowired private BorrowMapper borrowMapper; // cron 表达式:每天凌晨 2 点执行 @Scheduled(cron = "0 0 2 * * ?") public void checkOverdue() { List<BorrowRecord> overdueList = borrowMapper.selectOverdueRecords(new Date()); for (BorrowRecord record : overdueList) { // 实际项目中这里发送站内信或邮件通知 log.info("借阅记录 {} 已超期", record.getId()); } } }

cron表达式0 0 2 * * ?表示每天凌晨两点执行,顺序是秒、分、时、日、月、周、年,?表示不指定周几。注意启动类上必须加@EnableScheduling注解,否则定时任务不会触发,主类的注解通常是@SpringBootApplication和@EnableScheduling一起写。

第二个是接口冒烟测试。用MockMvc验证借阅、归还核心链路是否正常:

@SpringBootTest @AutoConfigureMockMvc class BorrowFlowTest { @Autowired private MockMvc mockMvc; @Test void testBorrowAndReturn() throws Exception { // 先借书 mockMvc.perform(post("/api/borrow/add") .contentType(MediaType.APPLICATION_JSON) .content("{\"userId\": 1, \"bookId\": 1}")) .andExpect(status().isOk()); // 再归还 mockMvc.perform(post("/api/borrow/return") .contentType(MediaType.APPLICATION_JSON) .content("{\"recordId\": 1}")) .andExpect(status().isOk()); } }

注意post("/api/borrow/add")如果被登录拦截器拦住,要先做登录态模拟:测试前调用一次登录接口,把 session 存下来随请求发送。这个项目没引 Spring Security,最省事的办法是测试类里先走一遍登录流程,再执行借还。

这两个功能加起来,系统才算真正闭环。从那以后我每次拿到类似源码,都会强制走一遍「改端口 → 导数据 → 跑核心借还链路 → 跑一遍冒烟测试」的流程,再开始动业务。这套动作能过滤掉绝大部分基础问题,希望帮到你。剩下的就是按自己的需求去改,给Book实体加个封面字段,或给BorrowRecord加个预约状态,都可以在这个骨架上直接长出来。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 3:11:57

基于Flutter与window_manager的仿macOS桌面客户端实现指南

前阵子把一套基于 Flutter 和 window_manager 的桌面端模板整理出来了&#xff0c;形态上是仿 macOS 桌面风格——带壁纸、桌面图标、底部 Dock 栏、多窗口层叠&#xff0c;底层则是纯 Flutter 技术栈搭出来的客户端外壳。项目做完之后&#xff0c;不少朋友问这套东西怎么落地&…

作者头像 李华
网站建设 2026/10/7 3:11:31

Docker镜像加速器配置指南:华为云SWR实战与常见坑排查

刚接触Docker的朋友&#xff0c;大概率都经历过这样的场景&#xff1a;费了半天劲把Docker装好&#xff0c;然后兴冲冲地敲下docker pull mysql:8.0&#xff0c;结果进度条半天不动&#xff0c;或者每秒几十KB的速度慢慢爬&#xff0c;最后直接给你抛一个timeout或者EOF的报错。…

作者头像 李华
网站建设 2026/10/7 3:11:16

模板代码调试技巧:从FreeMarker到IDEA模板实战

说到模板代码调试技巧&#xff0c;很多人第一反应是“打开日志&#xff0c;打印变量”。这个方向没错&#xff0c;但真的不够。模板代码和普通业务代码最大的区别在于&#xff0c;它有两条上下文链&#xff1a;一条是模板引擎自己的运行栈&#xff0c;另一条是模板要消费的数据…

作者头像 李华
网站建设 2026/10/7 3:10:55

Xcode添加文件全解析:引用、复制与移动的区别及选择策略

在iOS开发里&#xff0c;Xcode的“添加文件”功能大概是每个开发者每天都要碰到的操作&#xff0c;但很少人真正搞明白对话框里那几个选项的差异。我见过太多同行在项目里把同一个文件拖了多次&#xff0c;或者因为选错了添加方式导致文件丢失、构建失败&#xff0c;回头还要花…

作者头像 李华
网站建设 2026/10/7 3:10:27

SpringBoot3+Vue3高校资产管理系统毕业设计实战

简介&#xff1a;本资源是一套完整的高校资产管理信息系统毕业设计项目&#xff0c;面向计算机专业本科生及Java全栈初学者&#xff0c;解决高校资产登记、调配、报废与师生预约使用等实际管理痛点。压缩包共6个文件&#xff0c;含3个核心源码压缩包&#xff08;含前后端完整工…

作者头像 李华