资讯动态

SpringBoot+Vue选课排课系统:冲突检测算法与并发控制实战

发布时间:2026/10/13 22:50:38 来源:尧图企业网站定制
简介这份资源是面向高校计算机相关专业毕业设计场景的选课与排课管理系统完整方案基于SpringBoot后端与Vue.js前端构建适合正在准备毕设、需要参考前后端分离项目落地的学生也可作为学习Java Web技术栈的实践案例。压缩包为zip格式整体约17.55MB包含源码、SQL脚本、项目文档与演示PPT等核心文件其中源码覆盖后端接口与前端页面SQL脚本用于建表与初始化数据文档与PPT则支撑架构说明和答辩演示。目前已有559人学习下载具备一定参考热度。系统涵盖用户权限、课程信息、学生选课、教师排课与数据统计等模块后端采用RESTful API与数据库交互前端借助组件化与异步请求实现实时界面更新读者可据此理解JWT鉴权、课程冲突检测与选课统计等实现思路并在此基础上按需修改扩展形成自己的毕设成果。1. 选课排课系统为什么总在“冲突检测”这一步翻车每年毕业设计季选课排课管理系统都是被选得最多的题目之一但真正能跑通、能答辩、能写进简历的不到三成。问题几乎从不出在 SpringBoot 或 Vue 本身而是卡在“排课冲突检测”这个业务核心上——时间冲突、教室冲突、教师冲突三张网交织在一起很多同学写到一半发现算法根本兜不住最后只能改成手动排课糊弄过去。这个标题背后真正要解决的是用 SpringBoot 做后端规则引擎、Vue 做可视化课表把选课容量控制和排课冲突检测做成可演示、可复现的完整闭环。适合正在做毕设的计算机专业学生也适合想补一个教务类项目进作品集的初级开发者。下面我按实际能跑通的路径把源码结构、SQL 设计、冲突算法和文档 PPT 的产出逻辑拆开讲。2. 技术选型与库表设计为什么用 SpringBoot Vue 而不是别的2.1 后端选 SpringBoot 的真实理由教务类系统的核心诉求是“事务一致性”和“规则可配置”。选课瞬间要同时扣减课程容量、写入选课记录、校验时间冲突这三步必须在一个事务里完成否则就会出现“容量扣了但记录没写”的脏数据。SpringBoot 的声明式事务Transactional配合 MyBatis 的批量操作是这类场景最省心的组合。相比 Node.js 或 Python 后端SpringBoot 在毕业设计答辩时更容易讲清楚分层架构Controller-Service-Mapper 三层职责分明老师一问就能答。另一个现实原因是生态。排课冲突检测需要大量时间区间比较Java 的LocalTime、DayOfWeek处理周次和时间段比 JavaScript 的 Date 靠谱得多不用引入额外库。我一般会建议把“周次”建模成 1 到 20 的整数集合时间段建模成“第几节到第几节”这样冲突判断就是两个区间求交集逻辑干净。2.2 前端选 Vue 的落地考量Vue 在这个项目里承担两个关键界面学生选课页和课表可视化页。选课页需要实时反馈“这门课还剩多少名额”“和已选课程是否冲突”用 Vue 的响应式数据绑定比 jQuery 时代省太多代码。课表可视化用 Element UI 的表格组件做 7 列周一到周日乘 5 行节次的网格每个格子渲染课程卡片冲突的课程用红色边框标出答辩演示效果直接拉满。提示不要用 Vue3 的 Composition API 硬套毕设场景 Options API 反而更好讲代码结构对新手更友好。2.3 核心库表设计与 SQL 脚本要点库表设计是整个系统的地基设计错了后面全是补丁。核心六张表学生表、教师表、课程表、教学班表课程的具体开设实例、选课记录表、排课记录表。这里最容易踩的坑是把“课程”和“教学班”混成一张表——一门课可能由不同老师在不同时间开多个班必须拆开。-- 教学班表排课和选课的真正载体 CREATE TABLE teaching_class ( id BIGINT PRIMARY KEY AUTO_INCREMENT, course_id BIGINT NOT NULL COMMENT 关联课程表, teacher_id BIGINT NOT NULL COMMENT 授课教师, semester VARCHAR(20) NOT NULL COMMENT 学期如2024-2025-1, capacity INT NOT NULL DEFAULT 50 COMMENT 容量上限, selected_count INT NOT NULL DEFAULT 0 COMMENT 已选人数, class_room VARCHAR(50) COMMENT 上课教室, week_start INT NOT NULL COMMENT 起始周, week_end INT NOT NULL COMMENT 结束周, day_of_week TINYINT NOT NULL COMMENT 1-7 周一到周日, period_start TINYINT NOT NULL COMMENT 起始节次, period_end TINYINT NOT NULL COMMENT 结束节次, UNIQUE KEY uk_room_time (class_room, semester, day_of_week, period_start), INDEX idx_teacher (teacher_id, semester) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里有两个关键设计。第一selected_count冗余存储已选人数避免每次选课都去 count 选课记录表这是典型的空间换时间。第二uk_room_time唯一索引在数据库层面兜底教室时间冲突即使应用层算法有漏洞插入时也会被拦下。idx_teacher索引加速教师冲突查询。选课记录表要加UNIQUE KEY uk_student_class (student_id, teaching_class_id)防止同一学生重复选同一教学班。这个约束比在代码里查一遍再插入可靠得多高并发下代码检查会有竞态条件。CREATE TABLE course_selection ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, teaching_class_id BIGINT NOT NULL, select_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, status TINYINT NOT NULL DEFAULT 1 COMMENT 1已选 2已退, UNIQUE KEY uk_student_class (student_id, teaching_class_id), INDEX idx_class (teaching_class_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明status用逻辑删除而不是物理删除退课记录保留便于统计。select_time默认当前时间方便做“选课开放时间窗口”校验。3. 排课冲突检测算法从时间区间求交到教师教室三重校验3.1 冲突检测的数学模型排课冲突本质是三维空间的重叠判断时间维度、教室维度、教师维度。任意两个教学班只要在同一学期、同一周次范围、同一天、节次区间有交集且教室相同或教师相同就是冲突。用数学表达就是四个条件同时成立周次区间有交集max(week_start1, week_start2) min(week_end1, week_end2)星期相同day_of_week1 day_of_week2节次区间有交集max(period_start1, period_start2) min(period_end1, period_end2)教室相同 或 教师相同这个模型的好处是把复杂的排课问题降维成可枚举的区间比较一个教学班最多和几百个已有教学班比较单次检测毫秒级完成。3.2 后端冲突检测服务实现Service public class ScheduleConflictService { Autowired private TeachingClassMapper teachingClassMapper; /** * 检测待排教学班是否与已有排课冲突 * return 冲突列表空表示无冲突 */ public ListConflictVO checkConflict(TeachingClass target) { // 查出同学期所有已排教学班 ListTeachingClass existing teachingClassMapper .selectBySemester(target.getSemester()); ListConflictVO conflicts new ArrayList(); for (TeachingClass tc : existing) { if (tc.getId().equals(target.getId())) continue; // 排除自己 // 周次区间求交 boolean weekOverlap Math.max(tc.getWeekStart(), target.getWeekStart()) Math.min(tc.getWeekEnd(), target.getWeekEnd()); if (!weekOverlap) continue; // 星期不同直接跳过 if (!tc.getDayOfWeek().equals(target.getDayOfWeek())) continue; // 节次区间求交 boolean periodOverlap Math.max(tc.getPeriodStart(), target.getPeriodStart()) Math.min(tc.getPeriodEnd(), target.getPeriodEnd()); if (!periodOverlap) continue; // 教室或教师冲突 boolean roomConflict tc.getClassRoom() ! null tc.getClassRoom().equals(target.getClassRoom()); boolean teacherConflict tc.getTeacherId().equals(target.getTeacherId()); if (roomConflict || teacherConflict) { ConflictVO vo new ConflictVO(); vo.setConflictClassId(tc.getId()); vo.setType(roomConflict ? 教室冲突 : 教师冲突); conflicts.add(vo); } } return conflicts; } }逻辑说明先按学期过滤缩小范围再用四层判断逐级筛掉不冲突的记录避免全量比较。参数上weekStart/weekEnd是 1 到 20 的整数periodStart/periodEnd是 1 到 5 的节次编号。返回的ConflictVO带上冲突类型前端可以精确提示“与某某课程教室冲突”。注意这个算法是 O(n) 遍历n 是同学期教学班数量。如果学校规模大可以在 SQL 层用WHERE条件先过滤掉星期和节次不重叠的记录减少 Java 层循环。3.3 选课时的实时冲突校验学生选课时要校验的是“这门课和我已选课程是否时间冲突”逻辑和排课类似但比较对象是学生已选教学班集合。public void selectCourse(Long studentId, Long classId) { TeachingClass target teachingClassMapper.selectById(classId); // 1. 容量校验 if (target.getSelectedCount() target.getCapacity()) { throw new BizException(课程已满); } // 2. 时间冲突校验 ListTeachingClass myClasses teachingClassMapper .selectByStudentId(studentId, target.getSemester()); for (TeachingClass tc : myClasses) { if (isTimeOverlap(tc, target)) { throw new BizException(与已选课程《 tc.getCourseName() 》时间冲突); } } // 3. 扣容量 写记录同一事务 teachingClassMapper.increaseSelectedCount(classId); courseSelectionMapper.insert(studentId, classId); }isTimeOverlap就是前面四条件里的前三条周次、星期、节次不含教室教师因为学生冲突只看时间。increaseSelectedCount用UPDATE ... SET selected_count selected_count 1 WHERE id ? AND selected_count capacity这种带条件的更新利用数据库行锁保证并发安全比先查再改可靠。4. 前后端联调与课表可视化把冲突结果画出来4.1 后端接口设计与统一返回格式前后端联调最怕接口格式不统一。我一般会定一个ResultT包装类包含code、msg、data三个字段前端拦截器统一处理。排课相关接口设计成接口方法用途/api/schedule/checkPOST排课前冲突预检/api/schedule/savePOST保存排课结果/api/schedule/timetableGET获取课表数据/api/selection/selectPOST学生选课/api/selection/dropPOST学生退课/api/schedule/check返回冲突列表前端在排课表单提交前调用有冲突就弹窗提示不冲突才允许保存。这个“预检 保存”两步走的设计比直接保存后报错体验好很多。4.2 Vue 课表网格渲染课表用 Element UI 的el-table做 7 列每列对应一天行对应节次。数据从后端拿到的是教学班列表前端按day_of_week和period_start映射到网格坐标。// 把教学班列表转成课表二维数组 buildTimetable(classes) { // 5 行节次x 7 列星期 const grid Array.from({ length: 5 }, () Array(7).fill(null)); classes.forEach(cls { const row cls.periodStart - 1; const col cls.dayOfWeek - 1; // 跨节次的课程要占多行 for (let i cls.periodStart; i cls.periodEnd; i) { grid[i - 1][col] { ...cls, rowSpan: i cls.periodStart ? cls.periodEnd - cls.periodStart 1 : 0 }; } }); return grid; }逻辑说明rowSpan处理连堂课比如周三 3-4 节连上第一格占两行第二格设为 0 不渲染。前端渲染时用v-ifcell.rowSpan 0控制。冲突的课程用:class{ conflict-cell: cell.conflict }加红色边框答辩时一眼就能看出系统能识别冲突。4.3 选课页面的实时反馈选课页面每张课程卡片上显示剩余名额用进度条组件。点击选课后前端先本地校验一遍时间冲突把已选课程缓存在 Vuex 里本地通过再发请求减少无效请求。后端返回冲突时前端把冲突课程高亮并滚动到对应位置。这个交互细节在答辩演示时很加分。5. 避坑与常见问题排查那些让系统跑不起来的细节5.1 选课并发导致超卖现象压测时发现某课程容量 50最后选了 52 个人。原因select接口先查selected_count再更新两个请求同时查到 49都判断通过都执行加一。解决把容量校验和扣减合并成一条带条件的 UPDATEWHERE selected_count capacity根据返回的影响行数判断是否成功为 0 就抛“已满”。5.2 排课唯一索引导致保存失败现象排课时明明冲突检测通过了保存却报Duplicate entry。原因数据库的uk_room_time唯一索引只包含period_start但两节课可能period_start不同却节次区间重叠比如 1-2 节和 2-3 节period_start分别是 1 和 2索引不冲突但实际冲突。解决唯一索引改成(class_room, semester, day_of_week, period_start, period_end)不够因为区间重叠无法用唯一索引表达。正确做法是应用层算法兜底数据库索引只防完全相同的记录或者引入“节次位图”字段把 1-5 节映射成 5 位二进制重叠判断变成位与运算。5.3 周次区间边界判断错误现象第 1-8 周的课和第 8-16 周的课被判为不冲突实际第 8 周重叠。原因用了而不是。区间求交的正确写法是max(start1, start2) min(end1, end2)等号表示有交集。这个 off-by-one 错误在排课系统里非常常见血泪经验是写单元测试专门覆盖边界。5.4 前端课表跨节次渲染错位现象连堂课只显示了第一行第二行空白。原因rowSpan设置后后续行没有把对应格子设为不渲染Element UI 会多渲染一个空单元格导致错位。解决跨节次的后续行把数据设为null并用v-if跳过或者用:span-method统一控制。5.5 退课后容量没有回滚现象学生退课了但课程剩余名额没变。原因退课接口只更新了选课记录状态忘了selected_count - 1。解决退课和选课一样要放在事务里先更新记录状态再扣减计数两个操作要么都成功要么都回滚。6. 从能跑到能答辩文档、PPT 与演示脚本的产出技巧系统跑通只是及格线答辩能不能过还看你怎么讲。我一般会建议把整个项目拆成三个演示场景每个场景对应一个技术亮点。第一个场景是“排课冲突检测”。准备两门时间重叠的课现场点保存系统弹出冲突提示并高亮冲突课程。讲的时候重点说四层判断逻辑和数据库唯一索引兜底老师一听就知道你考虑了并发和边界。第二个场景是“选课并发控制”。用 JMeter 或者简单的多线程脚本模拟 100 个请求抢 50 个名额演示最终selected_count正好是 50没有超卖。这个演示比任何文字描述都有说服力直接证明你理解事务和行锁。第三个场景是“课表可视化”。展示一个学生的课表用不同颜色区分课程类型冲突课程红色标注。如果时间够可以加一个“一键检测全课表冲突”的按钮扫描所有教学班输出冲突报告这个功能在文档里写成“系统自检模块”。文档部分SQL 脚本要单独成文件按“建库-建表-初始化数据”三段组织初始化数据至少包含 3 个学院、10 个教师、20 门课程、5 个教学班保证演示时有数据可看。PPT 控制在 15 页以内结构是背景与痛点1 页、技术选型2 页、库表设计2 页、冲突算法3 页、演示截图4 页、难点与解决2 页、致谢1 页。每页文字不超过 6 行算法页用表格对比“冲突/不冲突”的判断结果比贴代码清晰。源码组织上后端按controller、service、mapper、entity分包前端按views、api、store、components分目录。README 里写清楚启动步骤先执行 SQL 脚本改application.yml里的数据库连接mvn spring-boot:run启动后端npm install npm run serve启动前端。这些细节看着琐碎但答辩老师经常会问“怎么部署”答得流畅就是加分项。最后一个习惯所有冲突检测的边界条件都写单元测试weekStart weekEnd、periodStart periodEnd、跨周次、跨节次各写一个 case。测试跑绿了再演示心里有底。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑