
课表管理系统毕业设计从零做到能答辩SpringBootVueMySQL完整落地笔记先说结论这篇内容不是教你怎么把源码下载下来解压就完事而是把课表管理系统这个毕业设计从需求梳理、数据库建模、后端接口、前端页面到最终部署和写论文的完整链路讲一遍。每个部分我都会结合自己做项目时踩过的坑来写尤其是那些一卡就是一整天的环境问题。如果你正准备做这个题目或者已经下载了一套二手源码却发现跑不起来这篇文章应该能帮你省下至少一周的时间。全文围绕SpringBoot、Vue、MySQL三条主线展开覆盖了从环境搭建到论文答辩常见追问的全部核心环节。1. 课表系统不只是排课表需求边界与功能拆解1.1 先从教务处排课的真实痛点说起很多同学拿到课表管理系统这个题目第一反应是不就是把课程名、教室、时间填进一个表格页面吗如果你按这个理解去做做出来的东西叫课程信息展示系统不叫课表管理系统评审老师一眼就能看出差距。真实的排课场景是这样的教务处需要在一学期开始前把几百门课程安排到有限的教室和时间段里同时要保证同一个班级不能同时上两门课、同一个老师不能同时出现在两个教室、教室容量要大于上课人数。这本质上是一个资源分配和冲突检测问题。课表管理系统的核心价值不在展示这一层而在排课和冲突校验这一层。1.2 毕业设计版本的功能取舍完整版的教学管理系统可能包含几十张表、十几个模块但毕业设计的时间通常只有三到四个月所以我建议按下面的优先级来做功能必做模块用户管理管理员、教师、学生三种角色登录、权限控制课程管理课程名称、学时、学分、开课学院等基础信息维护班级管理专业、年级、班级人数教室管理教室编号、容量、类型普通教室/机房/多媒体排课管理把课程分配到具体的时间段和教室带冲突校验课表查询学生按班级查课表、教师按个人查课表支持周次切换调课申请教师提交调课请求管理员审核加分模块有时间再做教学任务书导入导出Excel批量导入培养计划管理学生选课后的个人课表与排课结合做毕业设计有一个原则把必做模块做扎实比堆一堆半成品模块有用得多。评审老师最反感的是每个功能都只能点点看、一深问就露馅。你如果把冲突检测做完整了哪怕功能模块少一两个答辩的时候也能站得住脚。1.3 角色与权限评审老师第一眼会看的点登录后不同角色看到不同界面这是课表管理系统里最容易实现、也最容易被忽略的基础功能。很多同学做完登录就只是登录进去之后全是同一个管理界面这就很尴尬。我建议至少做到以下程度角色核心操作页面侧重点管理员所有模块的增删改查、排课审批、用户管理完整管理端教师查看个人课表、提交调课申请课表查询为主学生查看班级课表、查看教室课表只读查询为主后端的权限控制用SpringBoot的拦截器或者AOP都行前端路由守卫配合一下。这里不用上Spring Security那套重量级框架毕业设计的时间经不起折腾自己写一个简单的JWT拦截器完全够用而且答辩的时候你能把原理讲得更清楚。2. 技术选型为什么这套组合最不容易翻车2.1 SpringBoot版本怎么选热词里说的版本太高是怎么回事我注意到很多人在搜springboot版本太高这个问题这是真的踩坑高发区。SpringBoot 3.x 和 2.x 之间不是小版本升级有几个关键变化SpringBoot 3.x 强制要求 Java 17而很多人的电脑上装的是 JDK 8SpringBoot 3.x 底层是 Jakarta EE包名从javax.变成了jakarta.很多老教程、老代码在SpringBoot 3.x上直接编译不过我的建议是选 SpringBoot 2.7.x。理由很简单网上能找到的资料最多MyBatis-Plus、JWT、FastJSON 这些常用框架对 2.x 的兼容性最好而且 JDK 8 就能跑不用折腾环境。如果你的毕业设计题目没有硬性要求用最新版本就稳稳地用 2.7 系列。这不是技术落后的问题是把精力花在业务实现上而不是环境排错上。2.2 Vue2还是Vue3环境坑最少的选择Vue 的选型类似。Vue3 是现在的主流方向生态也已经很成熟了前端部分我推荐直接上 Vue3 Vite Element Plus。但这里有一个非常现实的问题很多人是第一次接触Vue连Node.js是什么都不太清楚。如果你在npm install这一步卡住了不要怀疑自己这是必经之路。Node版本太新或太旧、npm镜像源没设置、node_modules安装到一半中断这三个坑我都经历过。后面会专门写一节怎么处理。如果确实时间紧张Vue2 Element UI 也是能做的毕竟毕业设计不追求技术最新追求的是能跑、能答辩、能讲清楚。但既然你看到了这篇笔记我还是建议花半天时间把Node、Vite、Vue3的环境理顺一劳永逸。2.3 MySQL 5.7还是8.0以及MyBatis-Plus带来的开发效率数据库这块MySQL 5.7 和 8.0 都能满足课表系统的需求。5.7 的老教程多8.0 的性能和功能更好Windows安装也简单。我个人的选择是 MySQL 8.0原因是现在新买的云服务器自带的数据库基本都是8.0而且8.0的安装配置在Windows上有专门的安装包比5.7还省事。真正影响开发效率的是 MyBatis-Plus。用它的BaseMapper之后单表增删改查基本不需要写SQL这对毕业设计来说是非常实用的。但注意MyBatis-Plus 也不是万能的多表关联查询还是要自己在XML里写SQL。下面讲数据库设计的时候我会把哪些用MP自带方法、哪些要手写SQL说清楚。3. 数据库设计课表系统的地基怎么打3.1 六张核心表的字段规划课表管理系统最合理的表结构我按实际项目经验整理如下用户表 t_userCREATE TABLE t_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(255) NOT NULL COMMENT 密码BCrypt加密, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role varchar(20) NOT NULL COMMENT 角色ADMIN/TEACHER/STUDENT, teacher_id bigint(20) DEFAULT NULL COMMENT 关联教师ID, student_id bigint(20) DEFAULT NULL COMMENT 关联学生ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;课程表 t_courseCREATE TABLE t_course ( id bigint(20) NOT NULL AUTO_INCREMENT, course_name varchar(100) NOT NULL COMMENT 课程名称, course_code varchar(50) DEFAULT NULL COMMENT 课程编号, credit decimal(3,1) DEFAULT NULL COMMENT 学分, hours int(11) DEFAULT NULL COMMENT 总学时, college varchar(100) DEFAULT NULL COMMENT 开课学院, teacher_id bigint(20) DEFAULT NULL COMMENT 授课教师ID, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;班级表 t_classCREATE TABLE t_class ( id bigint(20) NOT NULL AUTO_INCREMENT, class_name varchar(100) NOT NULL COMMENT 班级名称如软件2101, grade varchar(20) DEFAULT NULL COMMENT 年级, major varchar(100) DEFAULT NULL COMMENT 专业, student_count int(11) DEFAULT NULL COMMENT 班级人数, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;教室表 t_classroomCREATE TABLE t_classroom ( id bigint(20) NOT NULL AUTO_INCREMENT, room_no varchar(50) NOT NULL COMMENT 教室编号如A201, capacity int(11) DEFAULT NULL COMMENT 容量, room_type varchar(50) DEFAULT NULL COMMENT 类型普通教室/机房/多媒体, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;时间表 t_time_slot—— 这个表很多人容易忽略但它是排课系统的核心辅助表CREATE TABLE t_time_slot ( id bigint(20) NOT NULL AUTO_INCREMENT, slot_name varchar(50) NOT NULL COMMENT 节次名称如第1-2节, start_time varchar(20) DEFAULT NULL COMMENT 开始时间如08:00, end_time varchar(20) DEFAULT NULL COMMENT 结束时间如09:40, weekday int(11) DEFAULT NULL COMMENT 星期几1周一 ... 7周日, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;排课表 t_schedule—— 也就是核心业务表CREATE TABLE t_schedule ( id bigint(20) NOT NULL AUTO_INCREMENT, course_id bigint(20) NOT NULL COMMENT 课程ID, class_id bigint(20) NOT NULL COMMENT 班级ID, teacher_id bigint(20) NOT NULL COMMENT 教师ID, classroom_id bigint(20) NOT NULL COMMENT 教室ID, time_slot_id bigint(20) NOT NULL COMMENT 时间段ID, week_start int(11) DEFAULT 1 COMMENT 起始周, week_end int(11) DEFAULT 16 COMMENT 结束周, semester varchar(20) DEFAULT NULL COMMENT 学期如 2024-2025-1, status int(11) DEFAULT 1 COMMENT 1正常 0已调课 2已停课, PRIMARY KEY (id), KEY idx_class_time (class_id, time_slot_id), KEY idx_teacher_time (teacher_id, time_slot_id), KEY idx_room_time (classroom_id, time_slot_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.2 排课表怎么设计才能防冲突排课系统最核心的一个设计点在于怎么在数据库层面保证同一时间、同一班级只能有一门课。答案是在t_schedule表上建组合唯一索引ALTER TABLE t_schedule ADD UNIQUE KEY uk_class_slot (class_id, time_slot_id, week_start, week_end);这个唯一索引的作用是当你往表里插入一条新排课记录时MySQL会自己检查是否已经有同样的班级、同样的时间段、同样的周次范围内的记录存在。如果冲突会直接抛DuplicateEntry异常。但这里有一个问题week_start和week_end是范围条件不是精确相等所以单纯靠唯一索引只能在完全重复的周次区间生效如果出现部分重叠比如一个排1-16周一个排8-16周唯一索引是拦不住的。这种场景就得靠后端代码做二次校验。真实项目的做法是数据库唯一索引兜底 后端精确校验双保险。唯一索引防并发插入的极端情况后端的校验逻辑处理周次交叉这种业务规则。3.3 最常用的三条查询SQL排课系统的查询主要集中在课表展示我把最核心的三条SQL拿出来说。按班级查周课表SELECT c.course_name, u.real_name AS teacher_name, cr.room_no, ts.slot_name, ts.weekday FROM t_schedule s JOIN t_course c ON s.course_id c.id JOIN t_user u ON s.teacher_id u.teacher_id JOIN t_classroom cr ON s.classroom_id cr.id JOIN t_time_slot ts ON s.time_slot_id ts.id WHERE s.class_id #{classId} AND s.semester 2024-2025-1 AND #{currentWeek} BETWEEN s.week_start AND s.week_end ORDER BY ts.weekday, ts.start_time;按教师查周课表把WHERE条件换成s.teacher_id #{teacherId}其余不变。查教室冲突SELECT COUNT(*) FROM t_schedule WHERE classroom_id #{classroomId} AND time_slot_id #{timeSlotId} AND semester 2024-2025-1 AND #{currentWeek} BETWEEN week_start AND week_end;我在做这个项目的时候课表查询接口是手写SQL的没有用MyBatis-Plus的Wrapper因为多表JOIN的情况Wrapper写着别扭。而课程、班级、教室这种单表维护就直接用MP自带方法开发效率很高。这样拆分的经验是单表用MP多表手写SQL这个比例在毕业设计里是最顺手的。4. 后端怎么写从空项目到可用的排课接口4.1 项目骨架与分层后端项目用的是标准的Controller-Service-Mapper三层结构加上entity、vo、dto、config、common几个包。完整结构如下course-schedule-backend/ ├── src/main/java/com/example/courseschedule/ │ ├── controller/ # 接收前端请求 │ │ ├── AuthController.java │ │ ├── ScheduleController.java │ │ ├── CourseController.java │ │ └── ClassController.java │ ├── service/ # 业务逻辑 │ │ ├── ScheduleService.java │ │ └── impl/ScheduleServiceImpl.java │ ├── mapper/ # MyBatis-Plus 接口 XML │ ├── entity/ # 数据库映射实体 │ ├── vo/ # 返回给前端的视图对象 │ ├── dto/ # 接收前端参数的传输对象 │ ├── config/ # CORS、拦截器配置 │ ├── common/ # 统一返回结果、异常处理 │ └── utils/ # JWT工具、密码加密工具 ├── src/main/resources/ │ ├── mapper/ # 手写SQL XML文件 │ └── application.yml └── pom.xml统一返回结果这一块我建议所有接口都返回下面这个结构前端处理起来非常一致public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message 操作成功; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }4.2 排课冲突检测核心逻辑的实现排课接口是这整个系统的重头戏我把核心逻辑写在这里这一段答辩的时候十有八九会被追问。Service public class ScheduleServiceImpl implements ScheduleService { Autowired private ScheduleMapper scheduleMapper; Override public ResultString addSchedule(ScheduleDTO dto) { // 1. 检查时间段是否在周一至周五 if (dto.getTimeSlotId() null) { return Result.error(400, 时间段不能为空); } // 2. 检查班级冲突 Integer classConflict checkConflict( class_id, dto.getClassId(), dto.getTimeSlotId(), dto.getSemester(), dto.getWeekStart(), dto.getWeekEnd() ); if (classConflict 0) { return Result.error(400, 该班级在此时间段已有课程安排); } // 3. 检查教师冲突 Integer teacherConflict checkConflict( teacher_id, dto.getTeacherId(), dto.getTimeSlotId(), dto.getSemester(), dto.getWeekStart(), dto.getWeekEnd() ); if (teacherConflict 0) { return Result.error(400, 该教师在此时间段已有课程安排); } // 4. 检查教室冲突 Integer roomConflict checkConflict( classroom_id, dto.getClassroomId(), dto.getTimeSlotId(), dto.getSemester(), dto.getWeekStart(), dto.getWeekEnd() ); if (roomConflict 0) { return Result.error(400, 该教室在此时间段已被占用); } // 5. 落库 Schedule schedule new Schedule(); BeanUtils.copyProperties(dto, schedule); schedule.setStatus(1); scheduleMapper.insert(schedule); return Result.success(排课成功); } private Integer checkConflict(String field, Long value, Long timeSlotId, String semester, Integer weekStart, Integer weekEnd) { return scheduleMapper.checkConflict(field, value, timeSlotId, semester, weekStart, weekEnd); } }对应Mapper XML里的核心SQLselect idcheckConflict resultTypejava.lang.Integer SELECT COUNT(*) FROM t_schedule WHERE semester #{semester} AND time_slot_id #{timeSlotId} AND status ! 2 AND #{weekStart} lt; week_end AND #{weekEnd} week_start if testfield class_id AND class_id #{value} /if if testfield teacher_id AND teacher_id #{value} /if if testfield classroom_id AND classroom_id #{value} /if /select这段代码的关键点在于周次重叠的判断newWeekStart oldWeekEnd AND newWeekEnd oldWeekStart只要满足这个条件就说明存在交叉需要提示冲突。这是排课系统里最容易写错的地方很多人只判断了相等没有判断范围交叉。4.3 周课表查询接口的设计课表的前端展示需要一个组装好的结构化数据。我返回给前端的结构是这样的{ code: 200, message: 操作成功, data: { weekday: 星期一, courses: [ { courseName: 高等数学, teacherName: 王老师, roomNo: A201, slotName: 第1-2节, startTime: 08:00, endTime: 09:40 } ] } }后端的处理逻辑是前端传classId或teacherId加currentWeek接口返回一个长度为7的数组每个元素代表周一到周日当天的课程列表。这样前端只需要遍历7次就能渲染出完整课表。实际开发中我用了MapInteger, ListCourseVO来做分组key存星期几value存当天的课程列表最后转成有序数组返回。这一步其实很简单但要注意排序必须按时间段顺序排而不是随机返回。4.4 登录认证JWT 拦截器课表系统的登录认证不复杂但建议做细一点。登录接口比较一下用户名密码密码用BCrypt加密存储登录成功后签发一个JWT前端后续请求带在Header里。拦截器只做三件事检查Header里有没有token、校验token是否过期、把用户信息塞进请求上下文。给不需要登录的接口放行路径比如登录接口其余全部拦截。有一个细节我印象很深JWT的密钥千万不要写在代码里。放到application.yml里用Value注入后面部署到服务器换密钥会很方便。5. 前端页面Vue3课表组件的落地细节5.1 初始化Vue3项目时最容易卡住的三个地方前端这块我先说环境问题因为太多人死在这一步。第一个坑是Node版本。Vite 5 要求 Node 18 以上但如果你的Node版本太新比如 Node 21某些依赖也会报错。稳妥的做法是装 Node 18.17 或者 20 的LTS版本。直接去官网下载安装包环境变量会自动配好然后验证一下node -v npm -v第二个坑是npm镜像源。国内不设置镜像源的话下载依赖会慢到怀疑人生npm config set registry https://registry.npmmirror.com第三个坑是创建项目时的选择。我用的是npm create vuelatest这个命令会问你一堆问题比如是否安装Vue Router、Pinia、ESLint等。对于课表系统我的选择是Vue Router要登录跳转和菜单路由都用得着Pinia可以不要一个简单的小项目用本地存储存用户信息就够了ESLint建议要虽然初期会报很多error但语法风格在后面写代码时会帮你规范很多5.2 课表网格的渲染思路课表页面的核心是7列周一到周日、N行节次的网格。我是用Element Plus的Table组件做的但故意没用它的合并单元格功能而是每个单元格单独存放一天一个时间段的所有课程。数据结构整理好了以后渲染逻辑其实很简单template el-table :datatableData border el-table-column label节次 width100 propslotName / el-table-column v-forday in weekDays :keyday :labelday aligncenter template #default{ row } div v-forcourse in row[day] :keycourse.id classcourse-cell p{{ course.courseName }}/p p{{ course.teacherName }}/p p{{ course.roomNo }}/p /div /template /el-table-column /el-table /template其中tableData是一个长度为节次数的数组每一项的结构大概长这样{ slotName: 第1-2节, 星期一: [{ courseName: 高等数学, teacherName: 王老师, roomNo: A201 }], 星期二: [], // ... 其余五天 }后端返回的数据在axios里做一次transform把原本按天分组的数组转换成上面这个按节次分组的结构就能直接渲染了。5.3 前端调接口时常见的跨域与字段问题跨域问题在前后端分离项目里基本都会遇到。解决办法有两种第一种是后端的CORS配置在Controller层加CrossOrigin或者在后端写一个全局CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二种是前端的Vite代理在vite.config.js里配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })我推荐两种都做因为本地开发用Vite代理最顺滑部署到服务器后前后端可能不在同一个服务下CORS配置就是兜底方案。字段问题也很常见。比如MySQL的字段名是create_time但前端拿到的是createTime这种驼峰命名。排查思路很简单先看后端返回的JSON是什么结构再用浏览器的Network面板对比实际返回和前端期望的字段是否一致。这个排查步骤如果你学会了前端联调阶段能少掉一半的头发。6. 那三件拖延进度的事环境、部署、论文6.1 环境配置的真实经验SpringBoot 2.7 JDK 8 Maven 3.6.3 的组合是我反复验证过最稳的。如果你用Idea新建项目注意Spring Initializr的Server URL可能默认连不上换成阿里的https://start.aliyun.com这个源支持SpringBoot 2.7.x的生成比默认的start.spring.io在国内快很多。MySQL安装的时候Windows上最容易出问题的是root密码设置和端口冲突。装完之后先检查3306端口netstat -ano | findstr 3306如果有进程占用可能是之前装过MySQL或者XAMPP需要先关掉。数据库连接串里建议加上serverTimezoneAsia/Shanghai否则会有时区错乱。6.2 从本地到服务器打包部署全流程部署方案我用的是最标准的后端打成jar包直接跑前端打包成静态文件放在Nginx里然后Nginx做反向代理把/api请求转发到后端端口。后端打包mvn clean package -DskipTests会在 target 目录下生成一个 jar 包上传到服务器后java -jar course-schedule.jar --server.port8080 --spring.profiles.activeprod前端打包npm run build生成的dist目录上传到 Nginx 的 html 目录然后在 Nginx 配置里加一段location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这里有一个关键点try_files那一行必须写。Vue Router 用的是 history 模式直接刷新/schedule这种路由时Nginx 会找不到对应的文件没有try_files就会404。配上之后所有路由都会回归到index.html由Vue Router自己处理。6.3 论文里哪些内容最容易被评审追问论文至少要有这几章选题背景与意义、技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望。其中评审老师最容易追问的内容集中在三块第一块是数据库设计合理性。你要能回答为什么班级、课程、教师要分开为什么排课表要有这些索引怎么保证数据一致性。第二块是核心算法的实现细节。课表系统里就是冲突检测的周次重叠判断你要能画出来、说清楚。答辩现场可以这样讲先按班级查重再按教师查重最后按教室查重每次都查周次是否交叉只要任何一个维度冲突就提示错误。第三块是测试用例的设计。不要只写能正常登录这种话。要写班级冲突时是否报错教师时间冲突时是否报错调课申请审核流程是否走通这种有业务意义的测试点。系统测试至少要有功能测试表和部分性能测试比如并发请求排课接口。还有一点论文章节的编号和图表别乱数据字典要完整。代码附到附录里就好正文别贴大段代码评审老师最不爱看那个。7. 最后分享一点做课表管理系统的个人体会这个项目做完一遍之后我最大的感受是课表管理系统的难点不在课表展示而在数据关系梳理和冲突规则定义。如果你能把这层想透前端那些表格渲染反而是最花时间的部分。我在联调阶段就遇到过“不同年份的周次同一学期不统一”的需求最后是通过在前端增加学期筛选器解决的后端查询也加了semester条件。另外给正在做或者准备做这个题目的同学一个建议留出至少一周的时间专门做测试和写论文。不要以为系统做完了就结束了测试报告里截图要截全、数据要真实论文里画的ER图要和数据库表结构完全对应这些在答辩时都是加分项。最后再分享一个小技巧课表页面可以加一个当前第几周的显示这个数据用后端接口提供前端拿到之后配合页面里的周次切换按钮展示效果会显得很完整。这个功能不大但答辩时可以作为一个亮点来讲因为它体现的是你对教学业务周期的理解。