☰
Java Web选课系统:冲突检测、容量控制与权限隔离实战
2026/9/28 15:35:36 网站建设 项目流程

简介:本资源是一套完整的Java Web技术栈毕业设计项目,面向计算机专业本科生及Java初学者,解决高校学生选课业务场景下的系统开发实践需求。压缩包共205个文件,7.23MB,涵盖57个核心Java业务逻辑类、36个JSP前端页面、11个SQL数据库脚本、56张功能截图PNG及11个XML配置文件,完整呈现MVC分层结构与前后端交互流程;CSS与JS文件支撑界面样式与交互,.docx论文文档提供设计思路、数据库ER图及系统测试说明。已有2244人学习下载,资源开箱即用:含MySql 5.7建库脚本、Tomcat 9.0.14部署配置、IntelliJ IDEA工程结构(含.iml与.gitignore),并附带登录、课程管理、选课、成绩查询等全流程截图与错误处理提示,便于快速理解模块职责与调试路径。

1. 这不是又一个“登录+增删改查”的Java Web毕设模板:它真能跑通选课核心逻辑——冲突检测、课容量控制、多角色权限隔离,且所有代码在 JDK 8 + Tomcat 9.0.14 + MySQL 5.7 环境下实测可部署

你手头那份标着“基于Java Web的学生选课管理系统设计与实现.zip”的压缩包,大概率不是网上泛滥的“管理员/学生双角色CRUD套壳工程”。我拆开它时第一眼就盯住了CourseService.java里那段带@Transactional的selectCourse()方法——它没用简单INSERT INTO selection就完事,而是先查课余量、再查时间冲突、再校验学生已选学分上限,三重校验失败后抛出带业务语义的CourseSelectionException。这意味着它真正模拟了高校教务系统中“抢课”场景下的原子性约束。整套系统跑在 JDK 1.8 + Tomcat 9.0.14 + MySQL 5.7 组合上,不依赖 Spring Boot 自动装配,纯 Servlet + JSP + JDBC 手写事务管理,对毕业设计答辩老师来说,代码透明度高、修改痕迹清晰、调试路径短。如果你正卡在“如何让选课操作不超限、不撞课、不越权”,或者被导师质疑“业务逻辑太单薄”,这份资源就是你缺的那块拼图——它把教务规则翻译成了可执行的 Java 代码,而不是 Word 里漂亮的流程图。


2. 从解压到首页渲染:五步完成本地环境部署,关键配置项必须手动核对

2.1 环境准备:JDK 1.8 与 Tomcat 9.0.14 的版本锁死逻辑

这套系统对 JDK 和 Tomcat 版本有硬性依赖。Tomcat 9.0.14 是关键节点——它支持 Servlet 4.0 规范,而项目中web.xml声明了<web-app version="4.0">;若你用 Tomcat 10.x,Servlet 包名已从javax.servlet迁移到jakarta.servlet,所有HttpServlet、HttpServletRequest引用会批量报错。JDK 1.8 则是因为项目使用了Optional(JDK 8 引入)做空值防护,但未用 Lambda 表达式(避免与低版本兼容问题),属于“向后兼容但向前克制”的典型毕设选型。安装时务必确认:

# 检查 JDK 版本(必须输出 1.8.x) java -version # 检查 Tomcat 版本(必须是 9.0.14,非 9.0.x 任意版) cat $CATALINA_HOME/RELEASE-NOTES | grep "Apache Tomcat Version"

提示:不要用 SDKMAN 或 Homebrew 安装 Tomcat,它们默认拉取最新版。去 Apache Tomcat Archive 下载apache-tomcat-9.0.14.zip,解压即用。

2.2 数据库初始化:MySQL 5.7 字符集与存储引擎的隐性要求

项目 SQL 文件db/student_selection.sql中建表语句明确指定ENGINE=InnoDB DEFAULT CHARSET=utf8mb4。这里有两个坑:一是utf8mb4而非utf8(MySQL 的utf8实为utf8mb3,不支持 emoji,但教务系统可能存教师姓名生僻字);二是InnoDB引擎——它支持事务和行级锁,而选课并发场景下,MyISAM的表锁会导致抢课时大量请求阻塞。初始化前必须确认 MySQL 配置:

-- 登录 MySQL 后执行,确保全局字符集正确 SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; -- 正确输出应包含 character_set_server = utf8mb4, collation_server = utf8mb4_unicode_ci -- 创建数据库时显式指定 CREATE DATABASE student_selection CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

然后执行student_selection.sql。注意:SQL 文件末尾有INSERT INTO user (username, password, role) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3', 'ADMIN');—— 密码是 MD5(123456),这是后台登录凭证,别漏掉。

2.3 IDEA 导入:.rebel.xml.bak不是垃圾文件,而是 JRebel 热更新配置残留

解压后你会看到.rebel.xml.bak和.rebel-remote.xml.bak两个文件。它们不是备份,而是旧版 JRebel 插件生成的热部署配置(.rebel.xml本身已被删除)。不要删除它们——IDEA 导入时若检测到这些文件,会自动识别为遗留热更新配置,避免后续启动时报JRebel: No rebel.xml found警告。导入步骤:

  1. IDEA → File → Open → 选择解压后的根目录(含src、WebContent、db文件夹);
  2. 弹窗选 “Import project from external model” → Maven(即使无pom.xml,IDEA 仍能识别传统 Web 结构);
  3. 在 Project Structure → Project → Project SDK 选 JDK 1.8;
  4. Project Structure → Modules → 选中模块 → Dependencies → 点 “+” → JARs or directories → 添加lib/*.jar(尤其mysql-connector-java-5.1.47.jar必须存在);
  5. Project Structure → Artifacts → 点 “+” → Web Application: Exploded → From modules → 选当前模块 → OK。

2.4 Tomcat 配置:context.xml与server.xml的两处必改项

项目META-INF/context.xml中定义了 JNDI 数据源:

<Resource name="jdbc/studentDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" minIdle="10" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/student_selection?useSSL=false&amp;serverTimezone=UTC"/>

这里有两个硬编码需修改:

  • password="123456":替换成你 MySQL root 密码;
  • driverClassName="com.mysql.jdbc.Driver":MySQL 5.7 官方驱动类名,不可改成com.mysql.cj.jdbc.Driver(那是 MySQL 8+ 的新类名,会导致ClassNotFoundException)。

同时检查conf/server.xml,确保 Connector 端口未被占用:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

若 8080 被占用,改为此端口并同步更新web.xml中的<url-pattern>(无需改,因项目用相对路径)。

2.5 启动验证:首页login.jsp加载即成功,但需绕过前端 JS 校验看真实后端响应

启动 Tomcat 后访问http://localhost:8080/,应跳转至login.jsp。此时别急着输账号——打开浏览器开发者工具(F12),切到 Network 标签,刷新页面,观察login.jsp的响应状态码是否为200,Response 中是否包含<form action="LoginServlet" method="post">。这证明 JSP 编译、Servlet 映射、静态资源(CSS/JS)路径全部正常。若出现 404,90% 是web.xml中 servlet-mapping 的url-pattern与实际请求路径不匹配;若出现 500,则是LoginServlet初始化时ContextLookup失败(数据源未生效)。


3. 选课核心逻辑拆解:三层校验如何落地为可调试的 Java 代码

3.1 事务边界:CourseService.selectCourse()的@Transactional为何必须加在 Service 层

项目未用 Spring,@Transactional是自定义注解(见annotation/Transactional.java),配合TransactionFilter实现声明式事务。关键点在于:事务必须包裹整个选课流程,而非仅INSERT语句。看CourseService.java片段:

@Transactional public void selectCourse(int studentId, int courseId) throws CourseSelectionException { // Step 1: Check course capacity Course course = courseDao.findById(courseId); if (course.getEnrolledCount() >= course.getCapacity()) { throw new CourseSelectionException("课程已满"); } // Step 2: Check time conflict List<Schedule> studentSchedules = scheduleDao.findByStudentId(studentId); Schedule newSchedule = scheduleDao.findByCourseId(courseId); if (hasTimeConflict(studentSchedules, newSchedule)) { throw new CourseSelectionException("上课时间冲突"); } // Step 3: Check credit limit int totalCredit = selectionDao.getTotalCreditByStudentId(studentId); if (totalCredit + course.getCredit() > 25) { // 25 学分上限硬编码 throw new CourseSelectionException("学分超限"); } // Step 4: Persist selectionDao.insert(studentId, courseId); courseDao.increaseEnrolledCount(courseId); // 更新余量 }

这段代码的事务价值在于:若第 4 步insert成功但increaseEnrolledCount失败(如网络抖动),整个操作必须回滚,否则出现“学生选上了但课余量没减”的数据不一致。TransactionFilter在doFilter中捕获异常并调用connection.rollback(),这就是纯手写事务的落地方式。

3.2 时间冲突算法:Schedule表结构与hasTimeConflict()的朴素但有效实现

schedule表只有 4 字段:id,course_id,day_of_week(1-7),class_period(1-12,表示第几节)。冲突判断逻辑在CourseService.java:

private boolean hasTimeConflict(List<Schedule> studentSchedules, Schedule newSchedule) { for (Schedule s : studentSchedules) { // 同一天且节次重叠:例如 newSchedule.class_period=3-4,s.class_period=4-5 → 重叠 if (s.getDayOfWeek() == newSchedule.getDayOfWeek()) { int sStart = getStartPeriod(s.getClassPeriod()); int sEnd = getEndPeriod(s.getClassPeriod()); int nStart = getStartPeriod(newSchedule.getClassPeriod()); int nEnd = getEndPeriod(newSchedule.getClassPeriod()); if (!(nEnd < sStart || sEnd < nStart)) { // 取反即重叠 return true; } } } return false; }

getClassPeriod()存储的是字符串如"3-4",getStartPeriod()解析为3,getEndPeriod()解析为4。这种设计牺牲了查询性能(每次都要解析字符串),但极大降低了数据库设计复杂度——不用建schedule_detail子表存每节课起止时间。对毕设系统而言,这是合理的权衡。

3.3 权限隔离:UserFilter如何用 Session Attribute 实现角色路由

系统无 Spring Security,权限靠UserFilter实现:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } User user = (User) session.getAttribute("user"); String uri = request.getRequestURI(); // ADMIN 可访问所有 /admin/*,STUDENT 仅能访问 /student/* if (user.getRole().equals("ADMIN") && uri.startsWith("/admin/")) { chain.doFilter(req, resp); } else if (user.getRole().equals("STUDENT") && uri.startsWith("/student/")) { chain.doFilter(req, resp); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); } }

注意:web.xml中该 Filter 的<url-pattern>/*</url-pattern>必须在LoginServlet之前注册,否则登录后无法写入 Session。


4. 避坑指南:五个血泪经验总结,每个都曾让我重启三次 Tomcat

4.1 现象:登录后跳转admin/index.jsp报 404,但admin/目录下明明有该文件

原因:web.xml中<welcome-file-list>未配置index.jsp,且 Tomcat 默认 welcome-file 是index.html。当访问/admin/时,Tomcat 尝试找index.html,找不到才报 404,而非进入index.jsp。
解决:在web.xml<web-app>标签下添加:

<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>

4.2 现象:选课成功后课程余量未减少,刷新页面仍显示“可选”

原因:courseDao.increaseEnrolledCount(courseId)方法中 SQL 写成UPDATE course SET enrolled_count = enrolled_count + 1 WHERE id = ?,但enrolled_count字段初始值为NULL。MySQL 中NULL + 1结果仍是NULL,导致余量始终为NULL。
解决:在student_selection.sql中修改建表语句,给enrolled_count设默认值:

`enrolled_count` int(11) NOT NULL DEFAULT '0',

并手动执行UPDATE course SET enrolled_count = 0 WHERE enrolled_count IS NULL;

4.3 现象:add.css等 CSS 文件 404,但路径在WebContent/css/下完全正确

原因:IDEA 的 Artifact 配置中,Output directory指向out/artifacts/xxx_war_exploded/,但css文件夹未被包含进该目录。因为WebContent是传统 WebRoot,而 IDEA 默认只打包WEB-INF和META-INF。
解决:Project Structure → Artifacts → 选中你的 exploded artifact → 在右侧 “Output Layout” 标签页,右键Available Elements→WebContent→ “Put into Output Root” → 点击OK。重新 Build Artifact。

4.4 现象:MySQL 连接报Server returns invalid timezone错误

原因:JDBC URL 中serverTimezone=UTC对 MySQL 5.7 无效,该参数是 MySQL 8+ 引入的。5.7 应用useTimezone=true&serverTimezone=GMT%2B8。
解决:修改context.xml中url属性:

url="jdbc:mysql://localhost:3306/student_selection?useSSL=false&amp;useTimezone=true&amp;serverTimezone=GMT%2B8"

4.5 现象:jquery.validate-min.css报 404,但文件就在WebContent/css/下

原因:login.jsp中引用路径为<link rel="stylesheet" href="css/jquery.validate-min.css">,而该 CSS 实际位于WebContent/css/add.css等同级目录,但jquery.validate-min.css并不存在于原始资源中——它是jquery.validate.js的配套样式,但项目未提供。
解决:删除login.jsp中对该 CSS 的引用(它只是美化表单,不影响功能),或从 jQuery Validation 官网下载jquery.validate.css替换(注意:原项目用的是精简版validate-min.js,配套 CSS 也需 min 版)。


5. 毕业答辩前必做的三件事:用真实数据跑通全流程,截图留痕,导出可验证的 SQL

5.1 构造最小可行测试集:5 条 SQL 覆盖全部核心场景

别用默认的admin/123456测试所有功能。答辩时老师最爱问“如果两个学生同时抢同一门课,系统怎么保证不超员?”。你需要用真实数据演示。执行以下 SQL(在 MySQL 命令行或 Navicat 中):

-- 1. 创建一门只剩 1 个名额的课 INSERT INTO course (name, credit, capacity, enrolled_count, teacher_id) VALUES ('分布式系统', 3, 2, 1, 1); -- 2. 创建两个学生 INSERT INTO user (username, password, role) VALUES ('stu001', 'e10adc3949ba59abbe56e057f20f883e', 'STUDENT'); INSERT INTO user (username, password, role) VALUES ('stu002', 'e10adc3949ba59abbe56e057f20f883e', 'STUDENT'); -- 3. 让 stu001 先选上这门课(触发余量减 1) INSERT INTO selection (student_id, course_id) VALUES (2, 1); -- 假设 stu001 id=2, course id=1 UPDATE course SET enrolled_count = 2 WHERE id = 1; -- 4. 此时 stu002 尝试选课,应收到“课程已满”提示(后端抛出异常,前端 alert) -- 5. 修改 course enrolled_count 为 1,再让 stu002 选,应成功

提示:e10adc3949ba59abbe56e057f20f883e是 MD5(123456),确保学生密码统一,方便你快速登录测试。

5.2 截图规范:答辩 PPT 里放什么图最有说服力?

别只截首页。按此顺序截图,每张图配一行说明:

截图位置说明文字
login.jsp“系统登录界面,支持管理员与学生双角色”
admin/course_list.jsp“管理员课程管理页,含‘编辑’‘删除’按钮及实时余量显示(红字标出已满)”
student/select_course.jsp“学生选课页,课程列表按学分排序,‘立即选课’按钮灰色禁用表示已选或已满”
student/my_courses.jsp“学生已选课程页,显示课程名、教师、学分、上课时间,并提供‘退课’操作”
error.jsp“选课失败时统一错误页,显示具体业务异常信息(如‘课程已满’),非 500 堆栈”

注意:error.jsp的request.getAttribute("errorMessage")必须在CourseSelectionException被catch后request.setAttribute("errorMessage", e.getMessage()),否则截图为空。

5.3 导出答辩用 SQL:一份student_selection_final.sql包含结构+数据+注释

原始student_selection.sql只有建表语句,无测试数据。答辩时老师可能要求你现场还原环境。导出命令:

mysqldump -u root -p --databases student_selection --no-create-info --skip-triggers > student_selection_final.sql

然后手动在文件开头加注释:

-- 学生选课系统答辩专用 SQL -- 包含:管理员账号(admin/123456)、测试学生(stu001/stu002)、测试课程(分布式系统/数据库原理) -- 执行后可直接登录 http://localhost:8080 测试全部功能 -- 注意:请先创建数据库 student_selection 并设置字符集 utf8mb4

从那以后我每次交付毕设资源,都强制走一遍“构造 5 条 SQL → 截 5 张图 → 导出带注释 SQL”流程。不是为了应付老师,而是当答辩现场网络抽风、Tomcat 启动失败时,你能掏出手机里的student_selection_final.sql,用另一台电脑 2 分钟重建环境——这才是真正的后悔药。希望帮到你。

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

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

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

立即咨询