简介:这是一套面向高校计算机相关专业课程设计、毕业设计及JavaWeb入门进阶学习者的实验室预约管理系统完整源码包,采用Java语言结合MySQL数据库开发,覆盖管理员、教师、学生三类角色的核心业务场景。系统功能涵盖用户管理、密码重置、公告发布、实验室信息维护、预约情况查看、高级搜索与排期表展示,教师端还支持个人预约与课堂预约、课堂信息管理、学生名册导入导出、课堂任务发布及实验资料上传等模块,适合作为课程作业参考或二次开发基础。资源包为zip格式,压缩后约42.69MB,内含项目源码、数据库脚本及配套说明文档,目录结构清晰,便于按模块查阅与调试。目前已有467人浏览学习,读者可借助完整的三层角色权限设计与预约排期逻辑,快速理解JavaWeb项目从建库、编码到功能联调的完整流程,并在此基础上进行功能扩展与个性化改造。
1. 实验室预约管理系统:从排课冲突到一键预约的 JavaWeb 落地路径
每到学期初,实验室门口总会上演同一幕:几拨学生拿着纸质登记本互相瞪眼,管理员翻着 Excel 反复确认「这间屋子到底谁批的」。实验室预约管理系统要解决的就是这个——把实验室资源、时间段、申请人三方关系用数据库锁死,用 Web 界面暴露给师生,让冲突在提交那一刻就被拦下。这套基于 JavaWeb 的方案,技术栈通常落在 Servlet/JSP 或 Spring Boot + MyBatis + MySQL 上,前端用 JSP 或 Thymeleaf 渲染,适合课程设计、毕业设计,也适合中小实验室做内部工具。源码、数据库脚本、文档三件套齐全,意味着你不用从零搭架子,重点放在跑通和改造成自己场景。下面按「先跑起来、再讲清结构、最后填坑」的顺序拆。
2. 环境搭建与项目跑通:IDEA 导入 JavaWeb 项目的最小闭环
2.1 JDK、Tomcat、MySQL 的版本对齐
JavaWeb 项目跑不起来,八成死在版本错配上。常见组合是 JDK 8 或 JDK 11、Tomcat 8.5/9.0、MySQL 5.7/8.0。JDK 17 配老版本 Tomcat 8 会出现Unsupported class file major version,MySQL 8 用旧版mysql-connector-java 5.1.x驱动会报Unknown system variable 'query_cache_size'。我一般先确认三件事:java -version输出、Tomcat 的lib目录里有没有对应驱动、MySQL 的character_set_server是不是utf8mb4。数据库连接串里serverTimezone=Asia/Shanghai和useSSL=false这两个参数在 MySQL 8 下不加,启动就抛时区异常。
2.2 用 IDEA 导入并配置 Artifact
导入步骤不复杂,但 Artifact 配置是新手最容易漏的一环。没有它,Tomcat 启动后访问 404。
# 1. 解压源码,用 IDEA 打开含 pom.xml 或 .iml 的根目录 # 2. File -> Project Structure -> Modules,确认 Sources 里 src 标记为 Sources Root # 3. Project Structure -> Artifacts -> Add -> Web Application: Exploded -> From Modules # 4. Run -> Edit Configurations -> Add -> Tomcat Server -> Local # 在 Deployment 标签页加入刚建的 Artifact,Application context 设为 /lab逻辑说明:Artifact 决定 Tomcat 部署时把哪些编译产物和资源打包进webapps。Application context是访问路径前缀,设成/lab后浏览器访问http://localhost:8080/lab/。参数上,Output directory保持默认即可,Before launch里要确保有Build Artifacts,否则改了代码不生效。如果项目是 Maven 结构,先在终端跑mvn clean package,确认target下生成 war 包再导入,能提前暴露依赖缺失。
2.3 数据库脚本导入与连接验证
数据库脚本一般是一个.sql文件,含建库、建表、初始数据。导入前先建库,字符集选utf8mb4,排序规则utf8mb4_general_ci。
CREATE DATABASE lab_booking DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE lab_booking; SOURCE /path/to/lab_booking.sql; -- 验证核心表是否就位 SHOW TABLES; SELECT COUNT(*) FROM lab_room; SELECT COUNT(*) FROM booking_record;逻辑说明:SOURCE命令在 MySQL 客户端里执行脚本,比图形化工具导入更少踩编码坑。参数上,如果脚本里写死了CREATE DATABASE,先注释掉那行再执行,避免和已有库冲突。验证阶段重点看lab_room(实验室表)和booking_record(预约记录表)有没有数据,空表说明脚本没跑完。连接验证在 IDEA 的 Database 面板里配一次,能连上再启动 Tomcat,省得在日志里大海捞针。
3. 数据库设计:预约冲突检测的表结构与 SQL 约束
3.1 核心表关系与字段取舍
预约系统的数据库设计,核心就三张表:实验室表、用户表、预约记录表。实验室表存房间号、容量、设备清单、开放状态;用户表存账号、角色(管理员/教师/学生);预约记录表存实验室 ID、申请人 ID、开始时间、结束时间、用途、审批状态。字段类型上,时间用DATETIME而不是VARCHAR,否则排序和范围查询会翻车。状态字段用TINYINT加注释,比VARCHAR省空间且索引效率高。
| 表名 | 关键字段 | 说明 |
|---|---|---|
| lab_room | id, room_no, capacity, status | status 0 停用 1 可用 |
| sys_user | id, username, password, role | role 区分 admin/teacher/student |
| booking_record | id, room_id, user_id, start_time, end_time, status | status 0 待审 1 通过 2 驳回 |
3.2 用 SQL 唯一约束和查询拦截时间冲突
光靠前端校验不够,并发提交时两个请求可能同时通过。常见做法是在应用层用「查询 + 插入」加事务,但更稳的是在数据库层加约束。MySQL 没有原生的时间段排他约束,所以退而求其次:在booking_record上建(room_id, start_time, end_time)的普通索引,应用层用SELECT ... FOR UPDATE锁行。
-- 冲突检测查询:同一实验室,时间段有重叠且状态为已通过 SELECT COUNT(*) FROM booking_record WHERE room_id = ? AND status = 1 AND start_time < ? -- 新预约的结束时间 AND end_time > ?; -- 新预约的开始时间逻辑说明:重叠判断的条件是「已有记录的开始时间早于新记录的结束时间,且已有记录的结束时间晚于新记录的开始时间」。参数顺序别搞反,第一个?是新预约的end_time,第二个?是新预约的start_time。返回大于 0 就说明冲突,直接驳回。这个查询走room_id索引,数据量不大时够用。如果实验室数量上千、预约记录上百万,再考虑按时间分区或引入 Redis 做缓存锁。
3.3 审批状态流转与软删除
预约记录不要物理删除,用status字段做软删除和状态流转。待审(0)→ 通过(1)或驳回(2),取消预约把状态改成 3。这样历史记录可追溯,管理员查日志也方便。字段上加create_time和update_time,默认CURRENT_TIMESTAMP,排查问题时能还原操作时间线。
4. 后端接口与前端页面:预约提交、审批、查询的完整链路
4.1 Servlet 层处理预约提交与参数校验
以 Servlet 方案为例,预约提交的入口是一个doPost。参数校验不能省,空值、时间倒置、超出开放时间都要拦。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding("UTF-8"); String roomId = req.getParameter("roomId"); String startTime = req.getParameter("startTime"); String endTime = req.getParameter("endTime"); // 基础校验:非空 + 时间顺序 if (roomId == null || startTime == null || endTime == null) { resp.getWriter().write("{\"code\":400,\"msg\":\"参数缺失\"}"); return; } if (startTime.compareTo(endTime) >= 0) { resp.getWriter().write("{\"code\":400,\"msg\":\"结束时间必须晚于开始时间\"}"); return; } // 调用 Service 做冲突检测和插入 boolean ok = bookingService.createBooking(roomId, startTime, endTime); resp.getWriter().write(ok ? "{\"code\":200}" : "{\"code\":409,\"msg\":\"时间冲突\"}"); }逻辑说明:setCharacterEncoding("UTF-8")必须在取参数之前调用,否则中文用途说明会乱码。时间比较用字符串的compareTo只在格式统一为yyyy-MM-dd HH:mm:ss时成立,更稳妥的做法是SimpleDateFormat解析成Date再比。返回 JSON 而不是跳转页面,是为了配合前端异步提交。参数上,roomId建议用Integer.parseInt转一下,防止 SQL 注入拼接。
4.2 审批接口与角色权限过滤
审批接口只对管理员开放。常见做法是在web.xml里配filter,或者在 Servlet 里判断session.getAttribute("role")。
Integer role = (Integer) req.getSession().getAttribute("role"); if (role == null || role != 1) { // 1 表示管理员 resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } String recordId = req.getParameter("recordId"); String action = req.getParameter("action"); // pass 或 reject bookingService.updateStatus(Integer.parseInt(recordId), "pass".equals(action) ? 1 : 2);逻辑说明:角色判断放在业务逻辑之前,避免越权操作。action参数用字符串而不是数字,前端传参更直观。审批通过后可以加一步「发送通知」,课程设计里通常省略,实际部署时用邮件或站内信补上。
4.3 前端页面渲染与分页查询
查询预约记录要分页,否则数据一多页面卡死。JSP 里用 JSTL 遍历,后端算好offset和limit。
SELECT b.id, r.room_no, u.username, b.start_time, b.end_time, b.status FROM booking_record b JOIN lab_room r ON b.room_id = r.id JOIN sys_user u ON b.user_id = u.id ORDER BY b.start_time DESC LIMIT ?, ?;逻辑说明:LIMIT的两个参数分别是偏移量和每页条数,偏移量 =(当前页码 - 1)× 每页条数。JOIN 查询把房间号和用户名带出来,前端不用再发请求。参数上,每页条数设 10 到 20 比较合适,太小翻页频繁,太大渲染慢。如果查询条件带实验室或日期范围,在WHERE里动态拼接,注意用PreparedStatement防注入。
5. 避坑与排查:JavaWeb 预约系统最常见的 5 个翻车现场
5.1 中文乱码:从请求到数据库的全链路排查
现象:提交预约后,用途说明在页面上显示成问号或方块。原因:请求编码、响应编码、数据库连接编码、表字符集四者不一致。解决:req.setCharacterEncoding("UTF-8")和resp.setContentType("text/html;charset=UTF-8")都要加;连接串加characterEncoding=utf8;建库时指定utf8mb4。Tomcat 8 以上默认 URI 编码是 UTF-8,但 GET 请求的中文仍可能乱,建议统一用 POST。
5.2 时间格式转换异常:java.text.ParseException
现象:提交预约时后台抛ParseException: Unparseable date。原因:前端传的时间格式和SimpleDateFormat的模式不匹配,比如前端传2024/01/01 10:00,后端按yyyy-MM-dd HH:mm:ss解析。解决:前后端约定统一格式,或者在解析前做一次替换。更稳的做法是用<input type="datetime-local">,它输出的格式是yyyy-MM-ddTHH:mm,后端解析时把T替换成空格再补:00。
5.3 数据库连接池耗尽:Too many connections
现象:系统跑一段时间后所有请求卡死,日志报Too many connections。原因:每次请求都DriverManager.getConnection新建连接,用完没关。解决:引入连接池,Druid 或 HikariCP 都行,配好maxActive和maxWait。同时检查代码里Connection、Statement、ResultSet是否在finally块里关闭。课程设计里用DBUtil封装获取和释放,能省很多事。
5.4 并发预约导致重复插入
现象:两个学生同时提交同一时间段,系统都提示成功。原因:冲突检测和插入之间有时间窗口,两个请求都查到「无冲突」。解决:在booking_record上加唯一索引不现实(时间段无法直接唯一),改用SELECT ... FOR UPDATE在事务里锁住该实验室的行,或者用 Redis 分布式锁。数据量小的场景,把冲突检测和插入放在同一个synchronized块里也能顶一阵,但只适合单机。
5.5 Tomcat 启动报 404:Artifact 没配对
现象:Tomcat 启动无报错,但访问http://localhost:8080/lab/返回 404。原因:Artifact 没加到 Deployment,或者Application context和访问路径不一致。解决:Run -> Edit Configurations -> Deployment,确认 Artifact 在列表里,Application context填/lab。如果项目是 Maven 多模块,检查pom.xml里packaging是不是war,webapp目录有没有被正确识别。
6. 从能跑到好用:预约系统的进阶改造与验证习惯
跑通只是起点,真正让这套系统在实验室里活下来,靠的是几个小改造。第一,把冲突检测从「提交时拦截」提前到「选择时间段时置灰」。前端用 AJAX 拉取某实验室某天的已占用时段,日历上直接标红,用户根本选不了冲突项,体验提升明显。第二,加一个「我的预约」页面,支持取消和改期,改期本质上是「取消旧记录 + 新建记录」,放在一个事务里做。第三,审批流加个备注字段,管理员驳回时写原因,学生能看到,减少来回扯皮。
验证方法上,我习惯用 Postman 或 curl 直接打接口,绕过前端看后端逻辑是否独立正确。比如提交一个时间倒置的预约,看返回是不是 400;提交一个冲突的,看是不是 409。接口层稳了,前端再出问题就只是展示层的事。数据库层面,定期跑一遍EXPLAIN看冲突检测查询有没有走索引,数据量涨到十万级时这一步很关键。
# 用 curl 验证预约接口的边界情况 curl -X POST http://localhost:8080/lab/booking \ -d "roomId=1&startTime=2024-06-01 14:00:00&endTime=2024-06-01 12:00:00" # 预期返回 {"code":400,"msg":"结束时间必须晚于开始时间"}参数说明:-d后面跟表单参数,多个用&连接。如果接口返回 500,先看 Tomcat 日志里的堆栈,定位到具体行号。这个习惯帮我省过很多次「前端看着没问题但就是提交失败」的排查时间。
最后说个血泪教训:数据库脚本一定要在导入前备份一份原始文件,改表结构时先SHOW CREATE TABLE留底。我有次手滑把booking_record的status字段类型改了,没留后悔药,只能重跑脚本,初始数据全丢。现在我的习惯是,任何涉及表结构的操作,先在测试库跑一遍,确认无误再上正式库。希望帮到你。
本文还有配套的精品资源,点击获取