ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Spring Boot的图书借阅管理系统:前后端分离与JWT鉴权实战

基于Spring Boot的图书借阅管理系统:前后端分离与JWT鉴权实战 简介这是一份基于Spring Boot的前后端分离图书借阅管理系统成品源码附带配套毕业设计论文适合计算机相关专业学生用于课程设计、毕业设计或二次开发。系统功能完整涵盖图书录入与维护、读者信息管理、图书借阅与归还、逾期处理、图书检索以及借阅统计等模块支持按书名、作者、ISBN等条件快速查找图书。后端采用Spring Boot整合JWT实现接口鉴权前端基于现代Web技术栈如Vue.js或JavaScript构建交互界面代码分层清晰便于修改界面和文案。压缩包共2000个文件大小约139.18MB其中以1692个Markdown文档为主用于记录项目说明和开发笔记另有Java源码、JS脚本、XML配置、JSON数据、docx运行须知及properties配置文件覆盖系统代码、配置、部署说明与论文素材。目前已有66人学习浏览资源可直接配置运行。拿到该资源后既能作为可运行的成品系统快速部署也能通过学习源码深入理解Spring Boot项目结构、前后端分离架构、权限认证流程和图书借阅业务逻辑同时配套文档可为论文写作提供充足参考。1. 为什么我建议你从这套图书借阅管理系统入门 Spring Boot如果你正卡在“跟着教程敲了个 CRUD但一打开真实项目还是懵”的状态springboot 图书馆管理系统这套前后端分离的图书借阅管理系统是我见过最适合用来补课的项目。它不是一个炫技的后台模板而是把 Spring Boot 后端、Vue 前端、MySQL 存储、JWT 鉴权、借还书业务闭环、论文文档全部串起来的完整工程。你能从里面看到真实项目怎么拆模块、怎么定接口、怎么处理借书还书这类有状态流转的业务而不是一个个孤立 Demo。这套系统对三类人特别对路准备毕业设计的学生想从前端或安卓转 Java 后端的开发者以及公司里需要快速搭一套内部图书管理后台的工程师。它的复杂度刚好卡在“看得懂”和“有东西可挖”之间——比商城少一层支付对账比博客多一层库存和借阅状态管理。接下来我会从架构、表设计、后端核心代码、前端联调一路拆到避坑和论文写作全程按可复现的标准来。2. 前后端分离的图书借阅系统架构拆分与表设计2.1 单体 JSP 项目 vs 前后端分离选型背后的权衡很多学校给的图书管理系统模板还是 JSP Servlet 那套老架构页面写在 Java 代码里改个按钮都要重启 Tomcat。而标题里明确写了“前后端分离版本”这意味着前端工程和后端工程是两个独立应用后端只提供 JSON 接口前端用 Vue 或 React 通过 HTTP 请求拿数据渲染页面。我一般会建议把这套系统当成 Spring Boot 前后端分离的入门样板来看原因有三个。第一职责边界清楚后端写业务逻辑和数据库交互前端管页面交互和路由跳转出了问题能直接定位到工程而不是在 JSP 的 HTML 碎片里翻 Java 代码。第二接口风格是现在企业里最主流的 RESTful你做完这套再去看公司项目的接口文档不会觉得陌生。第三它的部署方式灵活——开发时前端走 Node 代理生产时直接打成静态文件让 Spring Boot 托管一条命令起服务。但这套架构也有代价。最直观的是跨域问题前端跑在 8080 端口后端跑在 8081 端口浏览器会拦截非同源的请求你得配 CorsFilter 或者走代理。另外两个工程意味着两套启动流程、两套环境变量刚接触的人容易在“前端页面白屏但后端日志正常”这种状态下卡住。我的建议是如果你只是为了交作业或快速跑通别纠结微服务、Nacos 这些词老老实实把前后端分离的“分离”二字吃透比堆技术名词值钱得多。2.2 数据库表设计用户、图书、借阅记录、还书延期怎么建模图书借阅系统的核心表按我做过这类项目的习惯最少要拆五张用户表、角色表、图书表、借阅记录表、还书记录表也可以把还书合并进借阅记录。很多初学者喜欢把角色直接写成用户表里的一个字符串字段比如role admin这样做小 Demo 没问题但一旦要扩展权限粒度比如“图书管理员只能管图书不能管用户”就得回头改表结构。分离角色表是成本最低的扩展预留。借阅记录表是这套系统的业务核心字段设计会影响后面所有接口的写法。我的经验是至少要有这几列id、user_id、book_id、borrow_time借出时间、due_time应还时间、return_time实际归还时间未还则为 NULL、status0 在借 / 1 已还 / 2 逾期。把“应还时间”和“实际归还时间”分开存而不是只存一个状态值这样计算逾期天数时直接做日期减法不需要额外记一笔“逾期几天”。图书表里除了书名、作者、ISBN、分类必须有一个stock字段表示当前可借数量每次借书成功就减一还书就加一这就是后面要讲的库存并发控制的基础。用户表我建议保留create_time和update_time两个通用字段虽然小系统里看似没用但后面写论文画 E-R 图、做数据统计时这两个字段能派上大用场。表结构设计阶段最忌讳的是“等写到接口再补字段”因为你一旦把数据填进去、写好接口再回头加列就要改一堆 SQL 和实体类血泪经验。2.3 用 Navicat 初始化 MySQL 库表SQL 脚本与参数说明打开 Navicat 新建数据库时字符集我建议直接选utf8mb4排序规则选utf8mb4_general_ci。不要用utf8因为 MySQL 的utf8是阉割版存不了 Emoji 和生僻字utf8mb4才是完整的四字节 UTF-8。下面是核心建表脚本你直接复制到查询窗口执行即可-- 用户表 CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role varchar(20) NOT NULL DEFAULT USER COMMENT 角色ADMIN或USER, phone varchar(20) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 图书表 CREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT, isbn varchar(20) DEFAULT NULL COMMENT 国际标准书号, name varchar(200) NOT NULL COMMENT 书名, author varchar(100) DEFAULT NULL, category varchar(50) DEFAULT NULL COMMENT 分类如Java/文学/历史, stock int(11) NOT NULL DEFAULT 0 COMMENT 当前可借数量, total_stock int(11) NOT NULL DEFAULT 0 COMMENT 总库存, publish_date date DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 借阅记录表 CREATE TABLE borrow_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, book_id bigint(20) NOT NULL, borrow_time datetime DEFAULT NULL COMMENT 借出时间, due_time datetime DEFAULT NULL COMMENT 应还时间, return_time datetime DEFAULT NULL COMMENT 实际归还时间, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0在借 1已还 2逾期, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_book_id (book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明sys_user表把username设为唯一索引避免同一登录名重复注册password字段长度设 100因为 BCrypt 加密后的字符串长度约 60设太短会导致插入失败。borrow_record里对user_id和book_id都建了普通索引因为查询“某个用户借了哪些书”和“某本书被谁借走”是最常见的两个检索路径没有索引的话数据量一上去就会全表扫描。status字段用tinyint而不是字符串是为了省空间和方便条件查询配合 Java 端的枚举类做映射即可。注意一个细节due_time的默认值不要设置成CURRENT_TIMESTAMP因为它是业务字段由后端根据借书日期加 30 天计算后写入而不是记录创建时间。很多新手在这里偷懒直接复制上一张表的结构导致每本借出的书应还时间都是当前时间后面做逾期判断全部出错。3. Spring Boot 后端落地从自动装配到借阅核心流程3.1 项目骨架与依赖为什么建议选 Spring Boot 2.x 而不是 3.x这个标题既然出现“springboot开源项目”那项目骨架大概率是 Spring Initializr 生成的但具体版本需要你自己拿主意。我的建议是如果你的电脑装的是 JDK 8直接用 Spring Boot 2.7.x如果强行用 Spring Boot 3.x它底层要求 JDK 17很多学校机房和老服务器根本没装编译阶段就会卡住。这里顺带提一句 springboot 自动装配原理Spring Boot 的spring-boot-starter-parent帮你锁定了大量依赖版本SpringBootApplication注解里的EnableAutoConfiguration会扫描META-INF/spring.factories中的配置类按条件装配。你不需要背这些但面试或写论文时能解释清楚“为什么引入一个 starter 就能用”是加分项。pom.xml 里核心依赖一般是这几个spring-boot-starter-webWeb 容器和内嵌 Tomcat、spring-boot-starter-security做登录鉴权注意要关闭默认的表单登录、mybatis-plus-boot-starterORM 框架比原生 MyBatis 少写大量 XML 映射、mysql-connector-jMySQL 驱动、jjwt生成和解析 JWT token。MyBatis-Plus 在图书管理系统里特别好用的一点是它内置了分页插件和条件构造器查“某分类下库存大于 0 的书”这种需求不需要手写动态 SQL。3.2 用 JWT 做登录鉴权拦截器配置与 token 校验逻辑前后端分离的项目不能依赖 Session因为前端和后端不在同一个域名下Session 的 Cookie 默认行为会导致每次请求都带不上会话 ID。常见做法是后端生成 JWT token 返回给前端前端存在本地存储里每次请求在请求头加一个Authorization: Bearer token。登录接口的核心逻辑很直接接收用户名密码 - 用 BCrypt 校验密码 - 生成 JWT - 返回给前端。注意密码绝对不能用 MD5MD5 撞库太容易了Spring Security 自带的 BCryptPasswordEncoder 就是干这个的。JWT 的生成代码我用的是 jjwt 0.11.x 版本写法如下// JwtUtil.java - 生成与解析 JWT import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; public class JwtUtil { // 密钥至少要 32 个字符生产环境请放到配置文件中且不要提交到 Git private static final String SECRET_KEY your-secret-key-please-change-to-long-random-string; private static final long EXPIRE_TIME 24 * 60 * 60 * 1000; // 24小时有效 // 生成 token把用户 id 和角色放进 payload public static String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 解析 token校验签名和过期时间返回 Claims失败则抛异常 public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }逻辑说明sub字段存用户 IDrole作为自定义 claim 存角色名后续拦截器从Claims里取角色做权限判断。parseToken抛出的ExpiredJwtException和SignatureException要由全局异常处理器捕获转换成 HTTP 401 返回给前端而不是直接抛出堆栈给用户看。EXPIRE_TIME设 24 小时是折中方案太短用户老掉线太长安全风险高图书管理系统这个内部工具性质24 小时完全够用。拦截器方面继承HandlerInterceptorAdapter或实现HandlerInterceptor在preHandle里放行登录接口和静态资源其余接口全部校验Authorization头。别忘了在WebMvcConfigurer里注册拦截器并配置excludePathPatterns否则你会遇到一个经典翻车现场登录接口本身被拦截器拦住前端登录请求直接返回 401页面白屏。提示JWT 是无状态的服务端不知道 token 是否被注销。如果要做“管理员强制下线用户”这种功能需要在库里维护一个 token 黑名单或者引入 Redis小系统前期用不着但心里要有数。3.3 借书与还书接口事务、状态机与并发控制借书接口是这套系统里最容易写翻车的业务逻辑也是论文里值得拿出来当核心模块写的点。它的流程不是“往 borrow_record 插一条记录”这么简单必须同时做三件事检查图书库存大于 0、扣减库存、插入借阅记录并计算应还时间。这三件事必须放在同一个事务里任何一步失败都要回滚否则会出现“记录插成功了但库存没减”这种脏数据。写借书接口时我建议用Transactional注解标注在 service 方法上并且注意它只对 public 方法生效。下面是关键代码我用 MyBatis-Plus 的UpdateWrapper做条件更新这是解决超借问题最入门也最有效的手段// BorrowService.java - 借书核心逻辑 Transactional(rollbackFor Exception.class) public boolean borrowBook(Long userId, Long bookId) { // 第一层查询图书是否存在且可借 Book book bookMapper.selectById(bookId); if (book null || book.getStock() 0) { throw new BusinessException(图书不存在或库存不足); } // 第二层条件更新库存stock 0 是乐观锁的核心 // 这一步会返回影响行数如果为 0 说明并发下库存已被扣完 int rows bookMapper.update(null, new UpdateWrapperBook() .eq(id, bookId) .gt(stock, 0) // 关键条件库存必须大于 0 .setSql(stock stock - 1)); if (rows 0) { throw new BusinessException(库存不足借阅失败); } // 第三层插入借阅记录应还时间默认借书日期 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); borrowRecordMapper.insert(record); return true; }参数说明setSql(stock stock - 1)是让数据库自己执行加减法而不是先查出来减完再更新避免“读-改-写”三步中的并发空隙。gt(stock, 0)是乐观锁的简化版它的原理是即使两个请求同时进到方法里数据库层面也只有一个能更新成功另一个影响行数为 0直接抛业务异常。如果你追求更严格的并发控制可以在表里加一个version字段用 MyBatis-Plus 的Version注解做完整版乐观锁但图书管理系统的并发量远没到那个程度gt(stock, 0)已经能解决 99% 的问题。还书接口的逻辑稍简单但状态流转要注意先查借阅记录确认状态是“在借”然后更新return_time为当前时间、status改为“已还”最后把图书库存加回去。这里同样需要事务。逾期判断我建议在查询列表时动态计算而不是由一个定时任务去批量改状态——这两个方案的区别在于定时任务存在“今天没跑就漏判”的风险而动态计算每次请求都能拿到最新结果。3.4 自定义返回体与全局异常统一 API 规范前后端分离的联调效率很大程度上取决于返回结构是否统一。我见过最痛苦的对接是有的接口返回{code: 200, data: {...}}有的接口出错直接返回后端默认的 whitelabel 错误页前端要写一堆 if else 判断。从第一行代码开始就要把返回体锁死。我一般定义ResultT类包含三个字段code200 表示成功401 表示未登录500 表示服务端异常、message人话描述、data业务数据。同时配合ControllerAdvice写全局异常处理器把业务异常、参数校验异常、未知异常全部转成这个结构。这样前端拿到任何响应都能无脑判断code不需要关心 HTTP 状态码和响应体格式是不是对得上。全局异常处理器里最值得注意的是ExceptionHandler的优先级子类异常的处理方法优先级高于父类。比如BusinessException和MethodArgumentNotValidException参数校验要分开写不能只写一个Exception兜底否则前端收到的一定是“系统异常”而不是具体原因。这套机制做好之后前端联调会非常舒服接口报错信息能直接弹到页面上而不是连到后端控制台看日志。4. Vue 前端对接与跨域处理让页面把接口串起来4.1 Vue Element UI 目录结构与页面规划前端部分按标题里的“前后端分离”搭一个 Vue 2 Element UI 的工程是稳妥选择。不要选 Vue 3 Element Plus不是它不好而是网上图书管理系统的参考代码、博客踩坑记录绝大多数基于 Vue 2你遇到问题能搜到现成答案。别在这里给自己增加不确定成本。页面规划上最少要有登录页、图书列表页带分页和分类筛选、借阅记录页、我的借阅页、用户管理页管理员、图书新增/编辑对话框。路由要配beforeEach登录守卫没带 token 就强制跳转登录页。我见过很多半成品项目页面做了一大堆路由守卫没写结果用户直接在地址栏敲/admin就能进后台这放在论文和答辩里是个会被追问的漏洞。4.2 axios 封装与跨域配置开发态、生产态两种玩法前端调接口必须封装一个统一的 request 工具而不是在每个页面里写一堆 axios 实例。核心作用是请求拦截器里带上 token响应拦截器里统一处理 401 跳登录页。示例代码如下// src/utils/request.js import axios from axios import { Message } from element-ui // 创建实例并设置超时baseURL 走环境变量 const request axios.create({ baseURL: process.env.VUE_APP_BASE_API || http://localhost:8081, timeout: 10000 }) // 请求拦截器从 localStorage 取 token 放进请求头 request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理 code401 时跳回登录页 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } else { Message.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request逻辑说明baseURL走.env.development和.env.production两个环境文件开发时指向后端 8081 端口生产时用相对路径/api这样打包后能直接放在 Spring Boot 的static目录下。Authorization头的格式Bearer token要和后端拦截器解析时取的 header 名保持一致后端取AUTHORIZATION或Authorization都可以但要注意 Spring Boot 的HttpServletRequest.getHeader()是不区分大小写的前端写标准驼峰即可。跨域问题分两种环境处理。开发环境下最省事的方案不是在后端写 CorsFilter而是在 Vue 根目录新建vue.config.js配置 devServer 代理// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, // 后端地址 changeOrigin: true } } } }这样前端请求/api/user/login会被代理转发到http://localhost:8081/api/user/login浏览器看到的是同源请求跨域问题直接从源头消失。生产环境下前后端部署在同一端口Vue 打包后的静态文件由 Spring Boot 托管天然同源根本不存在跨域。所以我一般只在后端保留一个最基础的 CorsFilter 用于特殊场景开发主力走代理。4.3 把 Vue 打包放进 Spring Boot单包部署与页面刷新 404这里重点说“vue打包放进springboot中”这个热词对应的操作。先在前端工程根目录下建一个.env.production文件写入VUE_APP_BASE_API /api然后执行npm run build生成dist目录。接下来有两种方式放进 Spring Boot直接把dist里的所有文件复制到src/main/resources/static/下或者配置 Maven 插件在构建时自动拷贝。手动复制虽然笨但最直观适合理解原理。但这一步做完你马上会遇到一个经典问题路由用的 history 模式时在首页点着点着没毛病一到刷新或直接访问/books这种二级路径就 404。原因是刷新时浏览器把/books发给后端后端去找books这个 Controller 找不到就返回 404而前端路由是虚拟的只有 index.html 一个入口。解决办法是在后端写一个转发规则把所有非静态资源的路径转发到 index.html// WebConfig.java - 解决 Vue history 路由刷新 404 Configuration public class WebConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { // 把根路径转发到 index.html交给 Vue 路由自行渲染 registry.addViewController(/).setViewName(forward:/index.html); } Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 指定静态资源位置Spring Boot 默认的 static 目录不够用时扩展 registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/); } }但如果你用的是 Spring Boot 2.6 以上版本还要小心一个坑当接口路径和静态资源路径冲突时Spring Boot 的PathPattern匹配规则可能会导致静态资源请求被误判。我一般建议后端所有接口统一加/api前缀这样前端静态资源和后端接口在路径上彻底分开互不干扰。后面你反代到 Nginx 上也省心——直接按/api前缀转发到后端服务其他走静态文件。5. 系统避坑指南从数据库乱码到图书库存超借5.1 数据库连接乱码Unicode 参数漏写导致中文全变问号现象前端页面新增的图书书名含中文后端保存成功但数据库里存的是???或者乱码。原因数据库表字符集是utf8mb4没错但 JDBC 连接串没指定编码。MySQL 驱动默认用系统字符集跟服务端通信Windows 下经常是 GBK中文就被转坏了。解决改application.yml里的 JDBC URL加characterEncodingutf8和serverTimezoneAsia/Shanghai两个参数重启后端再测试。另外注意连接串里的useSSLfalse也要加上否则高版本 MySQL 驱动会报 SSL 警告虽然不是致命错误但日志刷屏影响排查。5.2 图书超借库存校验放在纯前端并发请求直接穿透现象管理员在前端页面看到某本书库存还剩 1 本同时两个用户快速点击借阅最终借出了 2 本库存变成负数。原因前端在发请求之前就判断了book.stock 0但这是基于页面上的静态数据做的判断并发场景下两个请求几乎同时到达后端后端如果只做了“查库存再更新”的两步操作就会存在时间差。解决按第 3.3 节的做法把库存扣减写成 SQL 层面的条件更新UPDATE book SET stock stock - 1 WHERE id ? AND stock 0并检查影响行数。影响行数为 0 就说明并发下库存已经没了直接抛业务异常。单纯在代码里先 select 再 update怎么加锁都有缝。5.3 跨域配置失效Filter 与拦截器顺序导致预检请求被拦现象前端axios请求后端接口控制台报CORS policy: No Access-Control-Allow-Origin后端明明配置了 CorsFilter 也不管用。原因Spring Boot 里如果有自定义 Filter比如 JWT 登录过滤器它执行顺序在 CorsFilter 之前预检请求OPTIONS请求没带 token被自定义 Filter 直接拦下返回 401根本没走到 CorsFilter 那一步。解决自定义权限 Filter 的doFilter里对OPTIONS请求无条件放行。同时可以用Order(Ordered.HIGHEST_PRECEDENCE)给 CorsFilter 标记最高优先级。如果你在接入 Spring Security还要注意http.cors()要开启否则 Security 的过滤器链也会把预检请求吞掉。5.4 时间字段偏移Jackson 序列化导致的 8 小时时区差现象前端展示的借书时间是 2025-06-24 02:00而数据库里存的是 2025-06-24 10:00差了 8 个小时。原因Jackson 默认使用 GMT 时区序列化时间而国内是 UTC8数据库时间被读出来后序列化时减了 8 小时。这是前后端分离项目里非常高频的一个问题和数据库存没存对无关。解决在application.yml里配置两件事——第一JDBC 连接串加serverTimezoneAsia/Shanghai第二设置 Jackson 的时区spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai如果用了 MyBatis-Plus 的TableField(fill FieldFill.INSERT)自动填充时间额外检查一下实体类里时间字段的类型是java.util.Date而不是java.sql.Datejava.sql.Date在序列化时拿不到时分秒会直接把时间截断到 00:00:00。6. 给论文配图与答辩准备把系统讲成一份技术方案6.1 用例图、时序图、ER 图怎么画才不像是抄的标题里那句“加论文”实际上很多同学拿到手的是一份已经写好的 Word 文档但它只能当模板不能直接交。你照着它改的时候最容易被老师盯住的就是图。如果你直接截图别人的博客配图数据库表字段名和你的代码对不上答辩时一问细节就露馅。我建议至少自己重画三张图。第一张是总体用例图画出三种角色——管理员、图书管理员、普通用户以及他们各自能做的操作。第二张是借书时序图重点画清楚用户、前端页面、Controller、Service、Mapper、数据库之间的调用顺序这张图是答辩时讲核心流程的道具。第三张是 E-R 图按第 2 章的表结构画标清楚主键外键和一对多关系。工具直接用 PlantUML 或 ProcessOn 都行别花时间折腾太复杂的绘图软件重点是字段和你的代码一致。论文的目录结构不要照搬网上那些“国内外研究现状”凑字数的大模板按真实项目的顺序写反而更好需求分析 - 系统设计 - 数据库设计 - 核心功能实现 - 系统测试。把 3.2 的 JWT 鉴权和 4.3 的部署配置好好写进核心功能实现里这两个点能跟评委讲出实际的技术细节比背十页“Java 是一种跨平台语言”有用得多。6.2 答辩前必查的四个逻辑点从功能演示到数据一致性最后说四个我在指导别人答辩时一定会追问的地方。第一演示时把后端日志窗口和数据库表都打开借一本书让评委看到库存从 5 变成 4、借阅记录多了一行这比单纯点页面更有说服力。第二主动讲一下“库存超借”你是怎么处理的哪怕你只是在 SQL 里加了个stock 0的条件也要把这个设计意图说出来这能直接体现你对业务问题的思考。第三把 JWT 的有效期、密钥配置位置、退出登录时前端怎么删 token 这几个问题准备好答案它们是高频追问点。第四如果实在被问到不会的问题不要慌着编坦诚说“这个点我在当前版本里还没深入实现但我了解它的基本思路是……”——这种回答比强行解释要体面得多。我自己的习惯是每做完一个功能点随手记一条“为什么这么做”的笔记最后写论文和准备答辩时全部用上了省掉大量回忆和翻代码的时间。这套图书借阅系统虽然不算什么高并发高可用的明星项目但它把你从零到一完整搭建一个前后端分离业务系统的路径完整走了一遍里面每个坑都是真实会踩的。希望帮到你。本文还有配套的精品资源点击获取
返回列表