
简介基于JavaWeb的图书管理系统完整源码与配套文档面向毕业设计、期末大作业与课程设计场景尤其适合JavaWeb初学者参考和学习。项目为个人手打高分98分作品代码含详细注释结构清晰新手也能快速理解并部署运行。资源共352个文件压缩包约64.79MB主要包含37个Java源文件、91个class编译文件、91个依赖jar包、14个HTML页面、13个CSS样式、12个JS脚本以及5个SQL数据库脚本、1份docx文档说明前端页面、后端逻辑、数据库脚本与部署组件一应俱全。已有544人学习下载。除完整可运行的图书管理功能书籍信息管理、用户信息管理、Excel导入导出等外还附带实践文档与数据库初始化脚本可直接作为高分毕设、期末大作业提交也便于二次开发时快速理解业务逻辑与扩展功能。1. JavaWeb 图书管理系统为什么「被做烂的题」反而最能拉分差图书馆里那类「扫码借书、逾期催还」的后台在企业里叫图书管理系统在课程设计里则是一道标准 JavaWeb 综合题用户登录、图书检索、借书还书、超期统计核心业务就这几件事。正因为它被做烂了反而成了检验 JavaWeb 基本功最好的试金石——真正拉开分差的从来不是功能多少而是分层清不清楚、事务有没有管住、文档能不能自圆其说。下面这套内容要解决的就是这个问题从技术栈选择到建表从核心代码到排错最后落回文档和答辩让你手里那份图书管理系统源码加文档说明既跑得起来也说得出口。适合正在赶课程设计、准备毕业设计或者想借一个完整的 javaweb 项目把 Servlet、JSP、MySQL 串成闭环的人。2. 技术选型与建表 SQLServletJSPMySQL 比 Spring Boot 更合适吗2.1 为什么不直接上 Spring Boot课程设计场景下的选型逻辑你打开搜索框热门词里几乎全是 Spring Boot 图书管理系统但我要先泼一盆冷水如果你的目标是「高分项目」ServletJSPMySQL 这套老组合反而更占便宜。原因很直白——Spring Boot 把请求流转、依赖注入、事务管理都封装成了黑匣子你写三行注解就能跑答辩老师问一句「一个请求从浏览器到数据库经历了哪些类、哪些方法」你很难答出细节。用原生 Servlet 写每一个环节都摆在明面上请求进了哪个 Servlet、service 层做了什么校验、DAO 里那条 SQL 是怎么拼的全部能指着代码讲这正好是 javaweb 课程设计的核心考核点。但这不代表 Spring Boot 一无是处。我一般会这样权衡如果项目要求里明确写了「采用 Spring Boot MyBatis」那就顺着要求做如果只写了「基于 JavaWeb」我优先选 ServletJSP把选型理由写进文档说明书这本身就是个加分论点。两者的对比可以做成一个表格放进文档对比项ServletJSPJDBCSpring BootMyBatis请求流转可见度每行代码都能指给老师看注解和代理把流程藏了一半搭环境成本TomcatMySQL十分钟能跑Maven 依赖下载就够折腾半天答辩追问风险低问题都在自己代码里高容易被问 starter 原理真实企业匹配度偏老但很多遗留系统仍在用主流跳槽简历更好看表格不是让你照抄而是建议你把这个对比写进自己的文档说明向老师传递一个信号这个题我做过技术选型评估不是只会复制粘贴。选型这件事没有绝对的对错关键是你的理由能不能在答辩现场站住脚。2.2 四张核心表的设计把「能跑」落到建表 SQL图书管理系统的表设计有套路但多数人照着别人的源码抄表结构抄完也说不清为什么这么建。我这里给出一份自己常用的最小表结构用户表、图书表、借阅记录表、分类表。四张表就能覆盖登录、查询、借阅、统计全部业务也方便扩展。-- 用户表区分管理员和普通读者 CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5 密文, real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 1 COMMENT 1-读者 0-管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表 SQL 里有两个容易被忽略的细节。一是password字段长度定了 64因为我们存的是 MD5 的小写在十六进制正好 32 位为什么用 64为了兼容 SHA-256后面想升级加密算法不用改表。二是role用 TINYINT 而不是 VARCHAR 存字符串省空间且判断快页面要显示「管理员/读者」时在代码里做映射不要直接在数据库里存中文。-- 图书表库存与总量分开设计 CREATE TABLE t_book ( id INT NOT NULL AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL COMMENT 国际标准书号, title VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) DEFAULT NULL, publisher VARCHAR(100) DEFAULT NULL, stock INT NOT NULL DEFAULT 1 COMMENT 当前可借库存, total INT NOT NULL DEFAULT 1 COMMENT 馆藏总量, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_isbn (isbn) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;stock和total分开存是我踩过坑之后总结出来的教训。曾经见过有人只存一个库存字段还书时加一、借书时减一看着没问题但图书破旧下架时要统计「这个月流失了多少本书」就完全无据可查。分两列之后下架图书只改 totalstock 用来做借阅可用性的判断统计口径清晰答辩时也更有话讲。图书表用isbn做了唯一键这是图书领域天然的商业主键比自增 id 更能防止重复录入。-- 借阅记录表业务的核心战场 CREATE TABLE t_borrow ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL COMMENT 借阅人, book_id INT NOT NULL COMMENT 图书, borrow_time DATETIME NOT NULL COMMENT 借出时间, due_time DATETIME NOT NULL COMMENT 应还时间, return_time DATETIME DEFAULT NULL COMMENT 实际归还时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-借出 1-已还 2-逾期, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_book (book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这条建表 SQL 的status字段值得多说几句。常见做法是借阅记录里只存借出时间和归还时间是否逾期靠查询时用当前时间去比 due_time 现场算这当然能算出来但每次列表页面都要做一次计算数据量大了 SQL 写起来很别扭。我倾向于在归还时或定时任务里把逾期状态落库查询时直接 where status2性能好也方便文档里写「逾期统计模块」。borrow_time和due_time都用 DATETIME 而不用 TIMESTAMP因为 TIMESTAMP 有 2038 年的上限课程设计虽然不会跑那么久但这个理由写进文档很加分。2.3 三层架构的工程边界Controller、Service、DAO 各管哪一摊很多源码包看起来乱根因是三层的职责边界没划清楚全部逻辑堆在 JSP 或 Servlet 里一个方法几百行老师看着就头疼。我在工程结构上一般坚持三条规则Servlet 只做参数接收、类型转换和页面跳转所有业务判断都进 ServiceDAO 只负责 SQL 执行和结果集映射不写 if-else 业务逻辑实体类只做字段存取不挂任何业务方法。这样每一层都能单独写单元测试虽然不是课程设计的硬要求但答辩时说到「我这段代码可测试性好」会明显比同龄人高一个段位。落到包结构上的话实体类放entity数据库访问放dao业务逻辑放service前端控制放controller工具类放util配置信息放resources。后面第四章会给完整的目录树这里先记住一个原则让别人通过目录就能猜到某个功能在哪这就是规范的价值。图书管理系统这种 CRUD 项目最忌讳的就是把代码写成一个大 Servlet 包打天下看着功能全跑通了实际上一点工程价值都没有。3. 从登录到借阅闭环三块核心代码与必调的参数3.1 用户登录Session 生命周期与 MD5 校验代码登录是所有 JavaWeb 项目的门面也是答辩时老师大概率会追问的第一块代码。这里给出一段常用的 LoginServlet 写法基于 Servlet 3.0 的注解方式省掉了一部分 web.xml 配置繁琐的映射代码。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); // 裸参数直接拦截减少数据库无效查询 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { resp.sendRedirect(login.jsp?errorempty); return; } User user null; try { // 数据库里存的是 MD5 密文这里加密后再比对 user userService.login(username.trim(), DigestUtils.md5Hex(password)); } catch (Exception e) { e.printStackTrace(); resp.sendRedirect(login.jsp?errordb); return; } if (user null) { resp.sendRedirect(login.jsp?errorwrong); return; } // 登录成功把用户对象挂到 session后续页面靠它做权限判断 HttpSession session req.getSession(); session.setAttribute(login_user, user); resp.sendRedirect(index.jsp); } }代码逻辑不复杂注意三处参数和边界的选择。第一req.setCharacterEncoding(UTF-8)必须写在读取参数之前否则 POST 请求中文用户名会乱码这一行能省很多事。第二密码不是明文判断而是先md5Hex再比对这个DigestUtils是 commons-codec 的工具类不建议自己手写一个不规范的 MD5 工具。第三登录失败用sendRedirect跳回登录页并带errorxxx参数方便页面提示具体错误原因登录成功则用重定向避免表单重复提交这里不能用forward否则刷新页面会再次触发登录请求。3.2 图书分页查询Limit 参数背后的三个配置点图书列表是读者最常用的功能不夸张地说大多数系统翻车都翻在分页上要么每页条数写死在前端要么 LIMIT 的两个参数顺序搞错。DAO 层常用写法如下public ListBook findPage(String keyword, int pageNum, int pageSize) throws SQLException { String sql SELECT id, isbn, title, author, publisher, stock FROM t_book WHERE title LIKE ? OR author LIKE ? ORDER BY id DESC LIMIT ?, ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ps.setInt(3, (pageNum - 1) * pageSize); // 偏移量从0开始 ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { ListBook list new ArrayList(); while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setIsbn(rs.getString(isbn)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setPublisher(rs.getString(publisher)); b.setStock(rs.getInt(stock)); list.add(b); } return list; } } }这段代码里三个需要调的参数你要心里有数。第一个是(pageNum - 1) * pageSize这是 LIMIT 的偏移量前端传pageNum1时算出来是 0第二页是 10很多人直接把 pageNum 传给 SQL结果第二页从第 11 条开始查丢掉第一条。第二个是LIKE的%拼接keyword 为空时不能拼出%%去全表扫应该在 DAO 外层判断空值直接走不带条件的查询。第三个是try-with-resourcesConnection、PreparedStatement、ResultSet 三个资源全部自动关闭必修课JavaWeb 连接 MySQL 的项目里漏关连接是内存泄漏的重灾区。3.3 借书与还书事务边界和日期计算怎么定借书流程是图书管理系统的核心业务也是事务最该出场的地方。很多人写出来的是先扣库存、再插借阅记录两条独立的代码中间任何一条失败都会造成数据不一致属于典型的高分项目硬伤。我这里贴出借书 Service 的核心方法public boolean borrowBook(int userId, int bookId) throws SQLException { String lockSql SELECT stock FROM t_book WHERE id ? FOR UPDATE; String updateStock UPDATE t_book SET stock stock - 1 WHERE id ? AND stock 0; String insertBorrow INSERT INTO t_borrow(user_id, book_id, borrow_time, due_time, status) VALUES(?, ?, NOW(), ?, 0); Connection conn DbUtil.getConnection(); try { conn.setAutoCommit(false); // 事务边界从这里开始 // 1. 行级锁锁住这本书避免并发借书超卖 int stock; try (PreparedStatement ps conn.prepareStatement(lockSql)) { ps.setInt(1, bookId); try (ResultSet rs ps.executeQuery()) { if (!rs.next()) { conn.rollback(); return false; } stock rs.getInt(stock); } } if (stock 0) { conn.rollback(); return false; } // 2. 扣库存这里再次检查 stock 0双保险 try (PreparedStatement ps conn.prepareStatement(updateStock)) { ps.setInt(1, bookId); ps.executeUpdate(); } // 3. 插入借阅记录截止时间取当前时间加 30 天 Calendar cal Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, 30); Timestamp dueTime new Timestamp(cal.getTimeInMillis()); try (PreparedStatement ps conn.prepareStatement(insertBorrow)) { ps.setInt(1, userId); ps.setInt(2, bookId); ps.setTimestamp(3, dueTime); ps.executeUpdate(); } conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { conn.rollback(); // 任何一步失败整体回滚 throw e; } finally { conn.setAutoCommit(true); DbUtil.close(conn); } }这块代码的亮点全在事务边界上。setAutoCommit(false)和commit/rollback三个方法构成了最朴素的 JDBC 事务扣库存和插入借阅记录要么一起成功要么一起失败不存在「书借出去了但记录没插上」这种脏数据。行级锁FOR UPDATE是并发场景下的关键参数虽然课程设计演示时没人会并发点击但代码里写了这一行答辩被问「多人同时借同一本书怎么办」就能讲出门道。日期计算用Calendar加 30 天比直接字符串拼日期更安全还书时判断逾期就是拿当前时间和due_time做比较这个逻辑放在还书的 Service 里代码风格保持一致。4. 源码目录与文档说明书让答辩老师十分钟看懂整个高分项目4.1 源码工程目录规范从「能跑」到「好读」我见过太多课程设计源码包一个 src 目录塞了三十个 ServletJSP 和图片混在一起没有 README下载的人不花两小时根本理不清结构。高分项目和低分项目的第一道分水岭就在目录。以下是我常用的 Maven 工程结构哪怕你的项目没用 Maven按这个思想组织普通 Web 工程也完全成立。book-manager/ ├── pom.xml ├── README.md ├── sql/ │ └── book_manager.sql # 建库建表脚本 测试数据 ├── src/ │ └── main/ │ ├── java/com/example/bookmanager/ │ │ ├── controller/ # Servlet接收请求、控制跳转 │ │ ├── service/ # 业务逻辑登录校验、借阅事务 │ │ ├── dao/ # 数据库访问SQL 与结果映射 │ │ ├── entity/ # 实体类User、Book、Borrow、Category │ │ └── util/ # 工具类DbUtil、MD5 工具 │ ├── resources/ │ │ └── jdbc.properties # 数据库连接配置 │ └── webapp/ │ ├── WEB-INF/ │ │ └── web.xml │ ├── jsp/ # 页面文件 │ │ ├── login.jsp │ │ ├── book_list.jsp │ │ └── borrow_list.jsp │ ├── css/ │ └── index.jsp注意几个容易被忽略的点。sql/目录放建表脚本是很多源码包缺失的老师最反感「拿到了源代码却建不出数据库」有完整的 SQL 脚本项目落地的第一步就顺畅了。jdbc.properties单独放而不是把数据库连接写在 Servlet 里因为改库名、改密码只需要动一个文件这是 JavaWeb 连接 MySQL 项目里最基础的解耦。README.md也不要敷衍写清楚三件事用什么版本的环境、怎么初始化数据库、从哪里启动入口。4.2 文档说明书写法从需求分析到测试用例的目录骨架源码文档说明标题里后半部分才是拉分的关键。文档说明书不是把代码拷进去凑页数而是讲清楚「为什么这么做」。我常建议的目录骨架如下给答辩老师递到手上时他扫目录就能看懂你的工作量第一章 项目概述背景、目标、开发环境JDK 版本、Tomcat 版本、MySQL 版本第二章 需求分析功能需求、角色分析管理员和读者的权限差异第三章 系统设计总体架构图用文字或简单框图、功能模块划分第四章 数据库设计ER 图、四张表结构说明、关键索引设计理由第五章 系统实现每个模块写出核心代码并配运行截图第六章 系统测试测试用例表格、测试结论第七章 总结与展望遇到的问题与解决过程、可扩展方向这里有个技巧每一章的「系统实现」不要全代码贴挑最有代表性的一段放进文档比如登录的 Session 处理、借书的 JDBC 事务代码要有注释旁边配截图。答辩老师看文档的时间有限他要的是「一眼知道你会什么」而不是「看完整份源码」。数据库设计那一章把 ER 图和前面第四章的表设计结合起来明确标注外键关系和索引用途这部分写透了借阅查询的 SQL 性能问题就不用费口舌解释。4.3 在 IDEA 里把 javaweb 项目跑起来部署配置清单不管源码写得多漂亮跑不起来就是零分。IDEA 运行 JavaWeb 项目配置踩坑的人特别多常见的问题是项目能编译却无法启动 Tomcat、页面报 404、数据库连接报错。我第一次带学生搭这个环境时几乎每个人都要卡半小时。这里整理一份我在 IDEA 里跑 javaweb 项目完整案例的最小配置清单按顺序做即可第一步确认 JDK 版本。JDK 8 和 Tomcat 8.5 是稳定组合不要一上来用 JDK 17 配老 Tomcat模块化问题会让你怀疑人生。第二步IDEA 里配置 Application Server。进入 Settings → Build, Execution, Deployment → Application Servers添加本地的 Tomcat 安装目录注意选的是解压目录而不是 bin 目录。第三步给 Web 模块加 Artifact。Project Structure → Artifacts选择 Web Application: Exploded把依赖的 jar 包加入 WEB-INF/lib。这一步漏掉运行时会报ClassNotFoundException对应的是 MySQL 驱动等依赖缺失。第四步配置 Run/Debug Configuration。点加号选 Tomcat Server → LocalDeployment 标签页添加刚才的 ArtifactApplication context 填/book-manager这个名字要和你访问 URL 保持一致。第五步检查jdbc.properties。URL 里的数据库名、用户名、密码、时区参数全部确认MySQL 8 的连接串必须加serverTimezoneAsia/Shanghai。这套清单里最容易出问题的是第三步和第五步。IDEA 新建的 Web 工程默认不会帮你建 Artifact很多人代码没问题运行按钮却是灰的就是这个环节没做。我一般的验证方式是先把 Tomcat 单独启动一次访问 8080 端口看到猫页面再回 IDEA 启动这样能把 Tomcat 自身问题排除在外不混淆故障源。5. 排错避坑JavaWeb 连 MySQL 最常见的 5 个翻车现场5.1 数据库连接失败时区、SSL 和驱动版本三连坑现象启动 Tomcat 后访问页面控制台报Cannot create PoolableConnectionFactory或者Communications link failure。原因MySQL 8 以后驱动版本、时区参数、SSL 安全连接三个点不配合就会连接被拒。JDBC URL 里没写serverTimezone必然报错SSL 没关在某些云数据库上也会握手失败。解决用下面这条连接串适配 MySQL 8 的常见姿势jdbc.urljdbc:mysql://localhost:3306/book_manager?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.usernameroot jdbc.password你的密码注意驱动类名从com.mysql.jdbc.Driver换成了com.mysql.cj.jdbc.Driver这是 MySQL 8 之后的强制要求。如果你用的是 5.x 的驱动连 8.x 的库驱动包本身不认新协议照样失败。遇到连接问题先做三件事pom 或 lib 里确认驱动 jar 版本、用 Navicat 单独测试连接串、把上面的 URL 格式抄过去。90% 的连接问题都在这三点里。5.2 中文乱码不显示请求、响应、数据库三层编码现象页面上显示「???」或者中文变成空登录输入中文用户名直接乱码。原因JSP 页面编码、Servlet 请求参数编码、数据库表编码三层不一致。最常见的是 JSP 页面是 UTF-8但 Servlet 里没设request.setCharacterEncoding(UTF-8)POST 参数按照 ISO-8859-1 解码中文自然碎掉另一个是建表时用了latin1改什么都不好使。解决三层全部统一到 UTF-8。filter filter-nameencodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这是 Tomcat 自带的编码过滤器放在所有 Servlet 前面全站请求都过一遍 UTF-8。建表时统一用CHARSETutf8mb4JSP 页面头部加% page contentTypetext/html;charsetUTF-8 %。三层都做了乱码问题基本绝迹。如果还有个别字段乱码检查是不是数据已经用错误编码写进库了这种只能清掉重导没有后悔药。5.3 借书超卖库存扣减和借阅记录不在同一个事务现象两个人几乎同时点借同一本书最后库存变成 -1或者借阅记录多了一条但库存没减。原因扣库存和插借阅记录是两条独立 SQL没有包在commit/rollback里。这是图书管理系统源码里最隐蔽的硬伤正常演示根本发现不了但代码评审一眼就能挑出来。解决用第三章给出的模板把两个操作放到同一个 Connection 的事务里再加一行SELECT ... FOR UPDATE锁住行记录。这里提醒一句FOR UPDATE必须在事务内且连接不关闭的前提下生效很多人把锁写对了却因为每次查询都新建连接导致锁自动失效等于白写。5.4 打开页面就 404/405找 servlet-mapping 的五个位置现象登录页能打开一提交表单就 404或者点按钮提示 405 Method Not Allowed。原因Servlet 的访问路径和表单 action 路径不匹配或者 doPost/doGet 只实现了一半。排查时按顺序检查五处表单的action有没有带工程上下文路径、WebServlet注解路径和 action 是否一致、web.xml里是否还残留了旧的 servlet-mapping 覆盖新路径、Servlet 类是不是重写了service方法却忘了写 doGet、404 时看 IDEA Console 里有没有报ClassNotFound导致 Servlet 根本没加载。405 几乎都是 doGet/doPost 缺失的问题这类错误报错信息很直白但新手经常盯着页面代码找半天忘了看方法名。5.5 IDEA 导入项目跑不起来Artifact 和 Tomcat 配置现象代码是同学给的源码也完整导入 IDEA 后运行按钮灰色或运行起来是 404。原因导入的工程没有配置 Web Artifact也没有添加 Tomcat 运行环境。IDEA 不像 Eclipse 那样自动识别 Web 工程开放导入后要手动关联。解决按第四章 4.3 的清单走一遍重点是 Project Structure 里把webapp目录标记为 Web 资源目录再新建 Artifact。另一个高频坑是导入后发现 JDK 版本和项目的编译级别不一致报invalid source release把 Project Structure 里 Project、Modules、Settings 三处的 JDK 全部统一到同一版本。这类问题不是代码问题是环境问题锻炼的是排查环境的能力遇多了就熟练了。6. 进阶与收尾加完这 3 个加分点再交付6.1 登录拦截器与借阅超期提醒两个低成本的加分点书还完了、代码跑通了这时候不要急着交。有两个小功能性价比极高其中一个就是登录拦截器。没有它用户直接在浏览器地址栏敲book_list.jsp就能绕过登录页访问系统这是课程设计最常见的功能漏洞也是答辩高频翻车点。加一个 Filter 就能解决WebFilter(/jsp/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(); if (session.getAttribute(login_user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这个是标准写法不需要再改参数。另一个加分点是借阅超期提醒不需要做短信邮件那种重功能只要在登录后的首页写一段查询SELECT COUNT(*) FROM t_borrow WHERE user_id? AND status2pending 的数据展示成一行红字「您有 N 本图书已逾期」。这两个功能代码量不大文档里却能各自占一小节功能价值不大但展示价值大能说明你考虑了「用户的真实使用场景」。6.2 交付前自测一张清单覆盖「能跑、敢答辩、不怕问」交付前最后两小时别急着打包源码先把这张自测清单走一遍功能项操作步骤预期结果登录成功读者账号正确密码跳转首页右上角显示姓名登录失败错误密码返回登录页并提示「用户名或密码错误」未登录访问地址栏直接访问 jsp 页面被拦截跳回登录页图书分页第二页显示第 11-20 条页码高亮且参数正确借书可借库存大于 0 的书库存减 1借阅列表出现新纪录借书超卖模拟库存为 1 的书连续借两次第二次提示库存不足无脏数据还书借出状态的书状态变已还库存加 1逾期展示造一条 due_time 小于当前时间的记录首页出现逾期提示这张表里最有价值的是「借书超卖模拟」和「逾期展示」这两行前者验证事务后者验证日期计算正是答辩时最可能被追问的两个技术点。我以前做课程设计栽过跟头功能全通老师一问「同一个用户反复点借书会发生什么」当场手心冒汗。后来所有项目交付前都会刻意造边界数据压一遍自己先把自己问倒答辩就从容了。希望这份笔记能帮你把图书管理系统做得比「能跑」再往前走一步既出得了手也答得上问——祝顺利。本文还有配套的精品资源点击获取