
简介一套基于SpringBoot的高校排课系统毕业设计资料包含源码与数据库SQL脚本主要面向计算机相关专业毕业生、课程设计学生以及需要快速搭建课表管理系统的开发者。资源围绕高校排课业务展开涵盖班级、专业、教室、教师、学生等实体关系以及排课流程中排前安排、排课分配、结果公示、按课表授课等环节的设计与实现思路尤其关注教学资源科学调度等关键问题。压缩包共235个文件以Java源码、HTML页面、XML配置、SQL脚本为主包含控制器、实体、测试类等典型SpringBoot分层文件以及Maven构建脚本和属性配置整体大小仅617KB轻量易部署。系统实现了登录、基础信息管理、排课处理与结果查看等功能代码结构清晰有助于理解需求分析如何转化为实际模块。已有1133人学习下载对正在准备高校排课方向毕业设计或希望参考完整源码与数据库脚本的开发者具有较好的借鉴价值。1. 高校排课系统毕设资料好找但命门在排课冲突“高校排课系统 源码数据库SQL脚本”这个标题在毕业设计里被搜了很多年。资料确实好找但把别人仓库里的脚本拉下来跑一遍多数人折在第一版数据库上字段对不上、初始化数据插不进、排课表里没有冲突检测只剩一张空课表。这门题的难点从来不在页面而是把教师、班级、教室、时间四个约束落进数据库再让代码和 SQL 脚本一起守住它。选这个题的学生和企业里做排期开发的同行面对的是同一件事给一组资源排期谁都不能冲突。毕设边界小正好适合把数据建模和事务逻辑做完整。想拿高分重点在建表脚本的严谨性、冲突检测的覆盖面和排课算法的可解释性。下面按最常见的工程路径把表结构、初始化脚本、冲突检测、贪心排课到验收自查完整走一遍每段代码都能直接抄进自己的项目里改。2. 数据库建模与 SQL 脚本排课系统的数据根基2.1 先理清 8 张核心表再谈写脚本排课系统最常见的翻车点是上来就建大宽表把 course_id、teacher_id、class_id、classroom_id、time 全塞进一张表。表面省事第三范式直接破功后面每写一条统计查询都要拖着一长串 GROUP BY。我一般让学生先画实体关系图保证下面 8 张表的边界是清楚的表名作用关键字段department院系dept_id, dept_nameteacher教师teacher_id, teacher_no, teacher_name, dept_idclass班级class_id, class_name, grade, dept_idcourse课程course_id, course_name, credit, total_hourscourse_plan开课计划plan_id, semester, course_id, class_id, teacher_id, weekly_hoursclassroom教室classroom_id, building, room_no, capacitytime_slot候选时间片slot_id, weekday, period_start, period_countschedule排课结果schedule_id, semester, plan_id, classroom_id, slot_id其中 course_plan 是很多人漏掉的一张表。它表达的是“这个学期、这个班级、上这门课、由哪位老师上”排课操作的对象其实是它而不是 course。没有这张中间表就表达不了“两个班合班上课”“一门课由两位老师各上八周”这类真实需求后面所有统计也无法按开课计划聚合。schedule 表通过 plan_id 间接拿到教师和班级这一跳是整套脚本里最值得在答辩时讲清楚的设计。2.2 建表 SQL主键、唯一约束和注释一次写全评审老师大概率会点开 SQL 脚本看两眼所以建表脚本我会把三样东西写全主键、唯一约束、字段注释。缺注释的表答辩时很容易被追问“这个字段到底存的是什么”。-- 教师表 CREATE TABLE teacher ( teacher_id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) NOT NULL COMMENT 教师工号, teacher_name VARCHAR(50) NOT NULL COMMENT 姓名, title VARCHAR(20) COMMENT 职称讲师/副教授/教授, dept_id INT COMMENT 所属院系ID, UNIQUE KEY uk_teacher_no (teacher_no), KEY idx_dept (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT教师表;teacher_no 加唯一约束因为工号在业务上天然唯一数据库不拦代码层就可能插入重复数据。dept_id 只建普通索引而不建物理外键是毕设里很实用的折中保留关联语义同时避免初始化数据时被外键顺序反复折腾。外键这个问题答辩时大概率会被问到我的回答口径是只在最关键的引用关系上用物理外键批量写入频繁的 schedule 表用索引代替换来的好处是插入性能和不依赖删表顺序。排课结果表是全项目约束最密的一张表CREATE TABLE schedule ( schedule_id INT PRIMARY KEY AUTO_INCREMENT, semester VARCHAR(20) NOT NULL COMMENT 学期如 2024-2025-1, plan_id INT NOT NULL COMMENT 开课计划ID, classroom_id INT NOT NULL COMMENT 教室ID, weekday TINYINT NOT NULL COMMENT 星期几1-5, period_start TINYINT NOT NULL COMMENT 开始节次1-10, period_count TINYINT NOT NULL DEFAULT 2 COMMENT 持续节数通常2或3, UNIQUE KEY uk_plan_week (semester, plan_id, weekday), UNIQUE KEY uk_room_time (semester, classroom_id, weekday, period_start), KEY idx_plan (plan_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT排课结果表;这里把 weekday、period_start、period_count 直接冗余在 schedule 里而不是每次 JOIN 字典表是为了让冲突检测 SQL 保持在最短路径上。uk_plan_week 防止同一个开课计划在同一天被排两次——一周上两次的课会被排到不同天uk_room_time 防止同一教室同一开始节次被重复占用。注意这两个唯一约束都拦不住“1-2 节”和“2-3 节”这种开始节次不同但实际重叠的情况那部分要靠第 3 章的区间检测兜底。2.3 初始化数据脚本可重复执行的种子数据方案数据库脚本一般拆成三个文件建库、建表、初始化数据。种子数据要覆盖三类能跑通主流程的最小数据、能触发冲突的边界数据、能让统计查询出效果的数据量。我在种子脚本开头固定写一段清理逻辑保证脚本可以反复执行# 以 MySQL 为例按顺序导入三个脚本 mysql -uroot -p 01_create_database.sql mysql -uroot -p -D course_scheduler 02_create_tables.sql mysql -uroot -p -D course_scheduler 03_seed_data.sql-- 03_seed_data.sql 开头可重复执行的清理 SET FOREIGN_KEY_CHECKS 0; TRUNCATE TABLE schedule; TRUNCATE TABLE time_slot; TRUNCATE TABLE course_plan; SET FOREIGN_KEY_CHECKS 1;TRUNCATE 之前临时关闭外键检查是为了绕开“先删子表还是先删父表”的顺序问题执行完立刻恢复为 1后续插入依然受外键约束保护。种子数据里我通常会故意造一条冲突比如两个班级的 course_plan 指向同一间教室的同一时间段这样演示冲突检测时不用现场造数直接给评审看拦截效果。2.4 表结构里常见的一个坑时间片怎么存时间片的存法是另一个高频翻车点。有的源码把时间片设计成一个大字典表一行一个“周一第 1-2 节”写死 25 行有的干脆在 schedule 里存 datetime。前者的问题是一旦要支持“三节连上”或“晚课两节中间休息”字典表的枚举就覆盖不全后者的问题在于课表是周历制用绝对时间反而不好处理公共假期和调课。我用 time_slot 表存候选集合它的字段就是 weekday、period_start、period_count 三个列的组合比如周一第 1-2 节就是 (1, 1, 2)。自动排课模块遍历的就是这张表的全部行schedule 里冗余的 weekday、period_start、period_count 又保证了冲突检测不用回查字典。代价是冗余字段可能不一致所以写入 schedule 的代码只允许从 time_slot 取值不允许页面手填数字。3. 排课冲突检测一条 SQL 拦下三类冲突3.1 三类冲突的判定条件排课系统运行时真正要拦的是三种冲突教师同一时间被安排到两门课、班级同一时间要上两门课、教室同一时间被两门课占用。它们的判定逻辑完全一样只是主体不同冲突类型判定条件教师冲突同一 semester、同一 teacher_id、同一 weekday时间区间重叠班级冲突同一 semester、同一 class_id、同一 weekday时间区间重叠教室冲突同一 semester、同一 classroom_id、同一 weekday时间区间重叠区间重叠的判定是一个经典的数学条件两个区间 [a, am) 和 [b, bn) 有交集当且仅当 a bn 且 b am。用上课节次的语言说就是新课开始节次必须早于已有课结束节次同时已有课开始节次必须早于新课结束节次。这个条件比开始节次相等严格得多能覆盖交叉场景。3.2 用一条 SQL 检查时间区间重叠检查冲突不需要存储过程一条带区间条件的 SELECT 就能完成。下面这条用于手动调课时检查教室是否被占用-- 新排课从第2节开始、持续2节即区间 [2, 4) SELECT COUNT(*) AS conflict_cnt FROM schedule s WHERE s.semester 2024-2025-1 AND s.weekday 3 AND s.classroom_id 101 AND s.period_start 2 2 AND s.period_start s.period_count 2;最后两行是核心period_start 2 2 要求已有课的开始节次早于新课的结束节次period_start period_count 2 要求已有课的结束节次晚于新课的开始节次。两个条件同时成立才判冲突这样“周一 1-2 节”和“周一 2-3 节”这种在第二节交叠的情况也能被准确识别。执行计划上uk_room_time 覆盖了 semester、classroom_id、weekday 三个前缀字段这条查询能走索引数据量几千条时毫秒级返回。3.3 教师和班级冲突要靠 JOIN不能只查 schedule教室冲突直接查 schedule 就够教师和班级冲突必须经过 course_plan 中转因为 schedule 里没有 teacher_id 和 class_id。对应的检测 SQL 要 JOIN 一次-- 检查教师冲突新排课前先查该教师是否已有重叠安排 SELECT COUNT(*) AS conflict_cnt FROM schedule s JOIN course_plan cp ON s.plan_id cp.plan_id WHERE cp.teacher_id #{teacherId} AND s.semester 2024-2025-1 AND s.weekday #{weekday} AND s.period_start #{endPeriod} AND s.period_start s.period_count #{startPeriod};班级冲突的写法一样把 cp.teacher_id 换成 cp.class_id 即可。这里容易踩的坑是忘记 JOIN 条件导致全表扫描所以 cp.plan_id 和 s.plan_id 两个字段都建了索引。代码层的完整防线分三步先调查询接口给前端返回冲突原因再用事务包住检查与插入最后靠数据库唯一约束兜底并发场景插入失败时捕获主键冲突异常转成友好提示。数据库唯一约束兜底这一点答辩时讲出来能明显拉开和普通项目的差距。4. 自动排课与手动调课把贪心算法写进源码4.1 贪心策略先排谁、为什么这么排自动排课在源码实现上九成是贪心而不是回溯搜索因为 16 周乘多个时间片的组合空间太大硬解没有工程意义。贪心的关键是排序先被选中的课会占据好窗口后选的只能捡剩下的。我常用的排序口径是三段式排序维度方向理由可选教师数量升序教师越固定的课越难调必须优先占位每周课时数降序课时多的课占用窗口多晚排容易排不进去班级年级升序高年级课程多先排降低整体失败率这个“先难后易”的顺序和操作系统里短作业优先的思路正好相反原因在于课表资源的约束是乘法级的一门课失败会导致后续一系列课连锁失败所以要把最难满足的约束放在最前面。4.2 自动排课核心代码Spring Boot MyBatis 的落地写法排课算法主体可以控制在几十行内我用 Spring Boot MyBatis 的写法演示Mapper 接口和方法签名从略重点看策略// 返回无法自动安排的课程计划供后续手动调课 public ListCoursePlan autoSchedule(String semester) { ListCoursePlan plans coursePlanMapper.findBySemester(semester); ListCoursePlan failed new ArrayList(); // 教师数量少、周课时多的课优先排 plans.sort(Comparator .comparingInt(CoursePlan::getTeacherCount) .thenComparingInt(p - -p.getWeeklyHours())); for (CoursePlan plan : plans) { boolean assigned false; for (TimeSlot slot : timeSlotMapper.listCandidates()) { // 教师冲突和班级冲突不满足则跳过 if (hasTeacherConflict(semester, plan, slot) || hasClassConflict(semester, plan, slot)) { continue; } Classroom room findFreeRoom(semester, plan, slot); if (room ! null) { scheduleMapper.insert(buildSchedule(plan, room, slot)); assigned true; break; } } if (!assigned) { failed.add(plan); // 收集失败项供手动调课 } } return failed; }这段代码有三个值得在答辩时展开的点。第一冲突检查被拆成 hasTeacherConflict 和 hasClassConflict内部各自执行一次第 3 章那种 EXISTS 查询互不干扰。第二findFreeRoom 按教室容量升序取第一个够用的教室避免 30 人的班占用 200 人阶梯教室这个细节能体现你考虑了资源利用率。第三failed 列表被返回而不是直接抛异常因为排课失败是预期内的情况需要给调课模块明确的操作对象。整个算法的时间复杂度是 O(课程数 × 时间片数)一百门课几十个时间片时秒级完成足够交差。4.3 调课保存必须排除自己这条记录自动排课失败后走手动调课调课本质上是对 schedule 表做一次 UPDATE。这里的坑在于保存时如果不排除当前正在编辑的记录冲突检测一查就是自己和自己冲突用户永远保存不了。-- 手动调课保存时排除当前 schedule_id 本身 SELECT COUNT(*) AS conflict_cnt FROM schedule s WHERE s.semester 2024-2025-1 AND s.weekday #{weekday} AND s.classroom_id #{classroomId} AND s.schedule_id #{currentScheduleId} AND s.period_start #{endPeriod} AND s.period_start s.period_count #{startPeriod};参数说明currentScheduleId 是正在编辑的那条排课记录的主键因为当前记录还指向旧时间片不排除就会自冲突startPeriod 和 endPeriod 是用户新选择的开始、结束节次。这段增删改查是整个系统里最容易写坏的地方建议在事务里先做检查再 UPDATE隔离级别用默认的 REPEATABLE READ 即可并发场景交给 uk_room_time 兜底捕获异常。5. 答辩前用 3 条自查 SQL 给排课数据做体检演示最有说服力的动作是当场跑一条查询证明 schedule 表里没有冲突记录。我把自查脚本固定成 3 条放在项目 sql/check 目录下每次演示前跑一遍任何一条查出非空结果都要先处理再上台。-- 检查1同一教室同一开始节次被重复占用 SELECT weekday, period_start, classroom_id, COUNT(*) AS cnt FROM schedule WHERE semester 2024-2025-1 GROUP BY weekday, period_start, classroom_id HAVING COUNT(*) 1;这条能查出精确重复但查不出“1-2 节”和“2-3 节”的交叉占用后者要用第 3 章的区间条件。所以自查脚本我会再放一条更严格的版本用自连接跑全量重叠检测-- 检查2同一班级经 course_plan 关联同时段上两门课 SELECT a.weekday, a.period_start, cp.class_id, COUNT(*) AS cnt FROM schedule a JOIN schedule b ON a.weekday b.weekday AND a.classroom_id b.classroom_id AND a.schedule_id b.schedule_id JOIN course_plan cp ON a.plan_id cp.plan_id WHERE a.semester 2024-2025-1 AND a.period_start b.period_start b.period_count AND b.period_start a.period_start a.period_count GROUP BY a.weekday, a.period_start, cp.class_id HAVING COUNT(*) 0;-- 检查3教室周利用率分布 SELECT classroom_id, ROUND(SUM(period_count) / 25.0 * 100, 1) AS usage_percent FROM schedule WHERE semester 2024-2025-1 GROUP BY classroom_id ORDER BY usage_percent DESC;检查3的分母 25 是每周 5 天乘每天 5 个时间块按两节一段折算如果系统支持三节连上分母要改成实际可用节次块数。三条语句里前两条期望是空结果第三条能看出教室负载的高低分布正好用来论证“自动排课的结果是均衡的”。跑完这三条之后再用 4.2 的排序参数跑一遍自动排课对比两次 failed 列表的长度就是一份可以直接写进答辩 PPT 的调优数据。本文还有配套的精品资源点击获取