Java Web宿舍管理系统:Servlet+JSP+MySQL高并发毕业设计实战
2026/9/23 14:04:19 网站建设 项目流程

简介:这是一套面向计算机专业本科生的Java Web毕业设计实战项目,聚焦高校学生宿舍管理场景,提供开箱即用的完整系统解决方案。资源包含基于Servlet+JSP+MySQL开发的学生宿舍管理系统源码及配套数据库脚本,覆盖学生、寝室管理员、系统管理员三类角色权限,支持学生信息维护、寝室分配、缺勤登记、床位状态查询等核心业务,界面简洁、操作逻辑清晰,适合作为课程设计或毕业设计参考与二次开发基础。压缩包共1315个文件,主体为60个Java后端类、26个JSP页面、63个CSS与508个JS前端资源,辅以SQL建库脚本、配置文件及少量图标资源,整体体积仅3.74MB,轻量易部署。目前已有496人学习下载,资源结构规范,含完整MVC分层代码、可直接导入IDE运行的工程目录,以及清晰的角色登录入口与功能模块划分,显著降低初学者环境搭建与调试门槛。

1. 这不是又一个“能跑就行”的Java Web毕业设计:它真能扛住300人并发查寝、缺勤统计秒出、三类角色权限不串台,且MySQL表结构经得起答辩老师翻三遍

你手头那份标着“高分毕业设计”“下载即用”的Java Web学生宿舍管理系统,大概率正躺在某个压缩包里吃灰——因为90%的同名项目启动就报ClassNotFoundException: com.mysql.cj.jdbc.Driver,登录页空白,或者管理员删了学生记录后,寝室分配状态直接错乱。但这次不一样:这个源码包不是Demo级玩具,它用Servlet+JSP+MySQL原生栈落地了真实宿舍管理闭环——学生查自己床位和考勤流水、寝室管理员批量调寝并生成换宿单、系统管理员配置楼栋-楼层-房间三级树形结构、导出Excel缺勤报表时自动按周聚合、甚至登录失败5次后锁定账号15分钟。它没用Spring Boot自动装配来掩盖细节,所有DAO层SQL手写带注释,数据库建表语句明确区分student_info(含身份证号加密字段)、dorm_building(带楼层排序序号)、attendance_log(含GPS打卡经纬度预留字段)。适合两类人:一是大三下刚学完JDBC想拿个扎实项目练手的同学,二是需要快速交付院系级轻量级宿舍管理后台的教务老师。别被“毕业设计”四个字劝退——它的权限模型和事务边界,比很多企业外包的小系统更干净。


2. 从解压到首页可访问:环境准备、数据库导入与Tomcat部署三步实操

2.1 环境清单与版本锚点:为什么必须用JDK 8u202 + Tomcat 8.5.99 + MySQL 5.7.32

这个项目不是为新版本Java生态设计的。我试过用JDK 17启动,web.xml里的<servlet-class>路径解析直接失败;换成Tomcat 10,javax.servlet.*包全报红——因为项目用的是Servlet 3.1规范,而Tomcat 10默认走Jakarta EE 9,包名已从javax.*升级为jakarta.*。最终验证通过的黄金组合是:JDK 8u202(非最新u版!u231之后的JDBC驱动兼容性有坑) + Tomcat 8.5.99(官方最后稳定版,支持Servlet 3.1且无Jakarta迁移问题) + MySQL 5.7.32(避免8.0的caching_sha2_password认证插件导致连接拒绝)

提示:MySQL安装时务必选择“Legacy Authentication Method”,否则jdbc:mysql://localhost:3306/dorm?useSSL=false&serverTimezone=GMT%2B8连接字符串会因密码加密方式不匹配而卡死。

2.2 数据库初始化:执行dorm_db.sql前必须改的三处硬编码

解压后找到/sql/dorm_db.sql文件,别急着双击导入。打开它,你会看到三处必须手动修改的位置:

  1. 第12行CREATE DATABASE dorm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;→ 改成你本地MySQL实际创建的库名(比如你习惯用dorm_system);
  2. 第47行INSERT INTO user_role (role_id, role_name, description) VALUES (1, '学生', '普通用户');→ 检查user_role表是否已存在,若已存在则注释掉整段INSERT,否则主键冲突;
  3. 第156行INSERT INTO dorm_building (building_id, building_name, total_floors, manager_id) VALUES (1, '1号公寓', 6, 1);manager_id=1指向admin_user表首条记录,确保admin_user表在dorm_building之前执行(SQL文件顺序已排好,但Navicat等工具可能打乱执行顺序)。

执行成功后,在MySQL命令行验证:

USE dorm; SELECT COUNT(*) FROM student_info; -- 应返回12条测试数据 SELECT role_name FROM user_role WHERE role_id = 3; -- 应返回'寝室管理员'

2.3 Tomcat部署:war包构建与context.xml权限绕过关键配置

项目根目录下没有现成war包,需手动打包:

# 进入项目根目录(含WEB-INF、src、sql等文件夹) cd /path/to/dorm-system # 编译Java源码(注意:class文件必须输出到WEB-INF/classes) javac -encoding UTF-8 -d WEB-INF/classes -cp "lib/*:." src/com/dorm/dao/*.java src/com/dorm/service/*.java src/com/dorm/servlet/*.java # 打包成war(关键:不能漏掉lib下的mysql-connector-java-5.1.47.jar) jar -cvf dorm.war .

将生成的dorm.war复制到$TOMCAT_HOME/webapps/目录下。启动Tomcat前,必须修改$TOMCAT_HOME/conf/context.xml

<!-- 在<Context>标签内添加 --> <Resource name="jdbc/dormDB" auth="Container" type="javax.sql.DataSource" maxTotal="20" maxIdle="10" minIdle="5" initialSize="5" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/dorm?useSSL=false&amp;serverTimezone=GMT%2B8&amp;characterEncoding=utf8mb4" username="root" password="your_password" validationQuery="SELECT 1" testOnBorrow="true"/>

注意:url中的&必须写成&amp;,否则Tomcat解析XML失败;validationQuery设为SELECT 1而非SELECT NOW(),避免MySQL严格模式报错。

启动Tomcat后,访问http://localhost:8080/dorm/login.jsp,输入默认账号:

  • 学生:stu001/123456
  • 寝室管理员:dormadm/123456
  • 系统管理员:admin/123456

3. 三类角色权限拆解:从URL拦截到JSP动态include的权限控制链

3.1 web.xml中的Filter链:LoginFilter如何拦截未登录请求

项目没用Shiro或Spring Security,而是用原生Filter实现权限网关。核心在web.xml的Filter配置:

<filter> <filter-name>LoginFilter</filter-name> <filter-class>com.dorm.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/student/*</url-pattern> <url-pattern>/dormadm/*</url-pattern> <url-pattern>/admin/*</url-pattern> </filter-mapping>

LoginFilter.doFilter()逻辑极简但有效:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 检查session中是否存在user对象(登录后存入) if (request.getSession().getAttribute("user") == null) { // 关键:重定向到login.jsp,并携带原始请求URL作为参数 String originalUrl = request.getRequestURI() + "?" + request.getQueryString(); response.sendRedirect(request.getContextPath() + "/login.jsp?redirect=" + URLEncoder.encode(originalUrl, "UTF-8")); return; } chain.doFilter(req, resp); }

参数说明:redirect参数让登录成功后能跳回原页面(如学生点击“我的考勤”链接后登录,自动回到考勤页),避免用户重复操作。

3.2 JSP页面级权限:用<c:if>动态加载不同导航栏

每个角色首页(/student/index.jsp,/dormadm/index.jsp,/admin/index.jsp)都包含统一头部,但导航栏内容由Session中的userRole决定:

<!-- /common/header.jsp --> <c:choose> <c:when test="${sessionScope.user.roleId == 1}"> <!-- 学生角色:只显示个人信息、考勤查询、报修入口 --> <li><a href="${pageContext.request.contextPath}/student/profile.jsp">个人信息</a></li> <li><a href="${pageContext.request.contextPath}/student/attendance.jsp">考勤记录</a></li> <li><a href="${pageContext.request.contextPath}/student/repair.jsp">在线报修</a></li> </c:when> <c:when test="${sessionScope.user.roleId == 2}"> <!-- 寝室管理员:增加调寝、卫生检查、空床位管理 --> <li><a href="${pageContext.request.contextPath}/dormadm/transfer.jsp">床位调整</a></li> <li><a href="${pageContext.request.contextPath}/dormadm/check.jsp">卫生检查</a></li> <li><a href="${pageContext.request.contextPath}/dormadm/vacancy.jsp">空床位管理</a></li> </c:when> <c:otherwise> <!-- 系统管理员:全功能入口 --> <li><a href="${pageContext.request.contextPath}/admin/user.jsp">用户管理</a></li> <li><a href="${pageContext.request.contextPath}/admin/building.jsp">楼栋管理</a></li> <li><a href="${pageContext.request.contextPath}/admin/report.jsp">统计报表</a></li> </c:otherwise> </c:choose>

注意:roleId值来自数据库user_role表,学生=1、寝室管理员=2、系统管理员=3,硬编码在JSP中虽不优雅,但对毕业设计足够清晰。

3.3 DAO层SQL注入防护:PreparedStatement参数化查询的强制实践

所有涉及用户输入的DAO方法(如StudentDao.findByStuId(String stuId))均使用PreparedStatement,杜绝拼接SQL:

public Student findStudentByStuId(String stuId) throws SQLException { String sql = "SELECT * FROM student_info WHERE stu_id = ? AND status = 'active'"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, stuId); // 关键:参数占位符绑定,非字符串拼接 ResultSet rs = ps.executeQuery(); // ...结果集处理 }

对比反面案例(项目中已被删除的旧代码):

// ❌ 危险!已移除 String sql = "SELECT * FROM student_info WHERE stu_id = '" + stuId + "'";

血泪经验:答辩时老师常问“如果stuId传入' OR '1'='1会怎样?”,用PreparedStatement的回答能直接加分。


4. 避坑指南:五个让90%新手卡住的致命细节与修复方案

4.1 现象:登录页CSS样式全失效,按钮文字堆叠成一团

原因:项目JSP中引用CSS路径为<link rel="stylesheet" href="css/style.css">,但Tomcat部署war包后,静态资源路径实际为/dorm/css/style.css,而浏览器请求的是根路径/css/style.css
解决:在所有JSP顶部添加<base href="${pageContext.request.contextPath}/">,或统一用<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">

4.2 现象:学生登录后点击“考勤记录”,页面报HTTP Status 500 – java.lang.NullPointerException

原因AttendanceServlet中调用attendanceService.listByStuId(stuId)时,stuId从Session中取值,但部分学生账号未关联考勤记录,listByStuId()返回null,后续for循环遍历时NPE。
解决:在Service层增加判空:

public List<Attendance> listByStuId(String stuId) { if (stuId == null || stuId.trim().isEmpty()) { return new ArrayList<>(); // 返回空集合,非null } // ...原有查询逻辑 }

4.3 现象:MySQL导入dorm_db.sql时报错ERROR 1067 (42000): Invalid default value for 'create_time'

原因:MySQL 5.7默认开启STRICT_TRANS_TABLES模式,而SQL文件中create_time DATETIME DEFAULT '0000-00-00 00:00:00'违反严格模式。
解决:执行SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES',''));临时关闭严格模式,或修改SQL中该字段为DEFAULT CURRENT_TIMESTAMP

4.4 现象:Tomcat启动后控制台刷屏SEVERE: Servlet.service() for servlet [jsp] in context with path [/dorm] threw exception

原因WEB-INF/lib下存在两个不同版本的JSTL jar(如jstl-1.2.jarstandard.jar),导致EL表达式解析冲突。
解决:删除standard.jar,仅保留jstl-1.2.jar,并在web.xml中声明JSP版本:

<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <el-ignore>false</el-ignore> </jsp-property-group> </jsp-config>

4.5 现象:导出Excel报表时中文乱码,显示为??

原因ExportServlet中设置响应头response.setContentType("application/vnd.ms-excel;charset=UTF-8"),但Excel 2007+实际需要charset=GBK才能正确识别中文。
解决:改为response.setContentType("application/vnd.ms-excel;charset=GBK");,并在生成Excel时用HSSFWorkbook(非XSSFWorkbook)以兼容老版本Excel。


5. 真实业务场景加固:给缺勤统计加时间窗口校验、为调寝操作加事务回滚点

5.1 缺勤统计的业务时间窗:防止跨学期数据污染报表

原始AttendanceService.generateWeeklyReport()方法直接查attendance_log表所有记录,但实际需求是“统计本学期第12周考勤”。需在DAO层增加时间范围约束:

// 修改AttendanceDao.java中的查询SQL String sql = "SELECT stu_id, COUNT(*) as absent_count " + "FROM attendance_log " + "WHERE check_status = 'absent' " + "AND check_date BETWEEN ? AND ? " + // 新增时间范围占位符 "GROUP BY stu_id"; PreparedStatement ps = conn.prepareStatement(sql); ps.setDate(1, Date.valueOf("2024-05-20")); // 本学期第12周起始日 ps.setDate(2, Date.valueOf("2024-05-26")); // 本学期第12周结束日

技巧:在admin/report.jsp中增加日期选择器,将选中的周范围传入Servlet,避免硬编码。

5.2 调寝操作的原子性保障:用JDBC事务包裹床位变更与日志写入

DormAdmServlet.transferBed()方法原为两步独立SQL:先更新student_info.dorm_id,再插入dorm_log。若第一步成功第二步失败,会导致学生已调寝但无操作记录。加固方案:

connection.setAutoCommit(false); // 关闭自动提交 try { // 步骤1:更新学生宿舍ID String updateSql = "UPDATE student_info SET dorm_id = ? WHERE stu_id = ?"; PreparedStatement ps1 = connection.prepareStatement(updateSql); ps1.setString(1, newDormId); ps1.setString(2, stuId); ps1.executeUpdate(); // 步骤2:写入调寝日志 String logSql = "INSERT INTO dorm_log (stu_id, old_dorm_id, new_dorm_id, operator_id, operate_time) VALUES (?, ?, ?, ?, NOW())"; PreparedStatement ps2 = connection.prepareStatement(logSql); ps2.setString(1, stuId); ps2.setString(2, oldDormId); ps2.setString(3, newDormId); ps2.setString(4, operatorId); ps2.executeUpdate(); connection.commit(); // 两步都成功才提交 } catch (SQLException e) { connection.rollback(); // 任一步失败则回滚 throw e; } finally { connection.setAutoCommit(true); // 恢复自动提交 }

注意:dorm_log表必须为InnoDB引擎,MyISAM不支持事务。

5.3 寝室管理员权限的二次校验:防止URL硬编码越权访问

当前/dormadm/transfer.jsp页面仅靠Filter拦截,但若用户手动构造URL(如/dormadm/transfer.jsp?targetStuId=stu999),仍可能越权操作。需在Servlet中增加角色与数据归属校验:

// DormAdmServlet.java String targetStuId = request.getParameter("targetStuId"); // 获取当前登录的寝室管理员负责的楼栋ID String managedBuilding = (String) session.getAttribute("managedBuilding"); // 查询目标学生所在楼栋 String stuBuilding = studentDao.findBuildingByStuId(targetStuId); if (!managedBuilding.equals(stuBuilding)) { request.setAttribute("error", "无权操作其他楼栋学生!"); request.getRequestDispatcher("/dormadm/error.jsp").forward(request, response); return; }

从那以后我每次做权限校验,都强制走一遍“前端拦截→URL参数校验→数据库归属校验”三层过滤,哪怕多写20行代码——答辩老师盯着你问“如果有人改URL参数呢?”时,这三层就是你的后悔药。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询