SpringBoot2+Vue3+MyBatis-Plus课表管理系统源码深度解析

发布时间:2026/10/7 12:37:42
SpringBoot2+Vue3+MyBatis-Plus课表管理系统源码深度解析
课表管理系统这种项目十个人里有八个是拿来做毕业设计或者课程设计的剩下两个是老师丢给学生练手。但说实话市面上的“课表管理系统源码”质量非常参差——有的后端还停留在SSHStruts2SpringHibernate时代有的前端用着十年前jQuery那一套拿回来想改点东西都无从下手。这篇要拆解的SpringBoot2Vue3MyBatis-PlusMySQL8.0版课表管理系统算是市面上比较新的技术栈组合后端用SpringBoot2做接口服务前端用Vue3配合Element Plus搭后台界面持久层直接用MyBatis-Plus写通用CRUD数据库落在MySQL8.0上还带一套完整的课程、教师、教室、排课数据模型。它能解决什么问题往浅了说是把“排课记录课表查看”这套流程跑通往深了说是一个完整的Web前后端分离练手项目覆盖从数据库设计、权限控制、接口开发到前端渲染的整条链路。适合正在做毕业设计、想熟悉最新前后端分离开发模式、或者想拿一套干净源码做二次开发的读者。下面我会从架构选型、数据模型、后端核心实现、前端渲染、本地部署几个维度逐一拆最后把我实际跑这个项目时踩过的坑单独列一节。这篇文章不是照着官方文档念说明书而是站在“我拿到这份源码之后怎么快速吃透它、改造成自己想要的样子”的角度来写。1. 整体设计思路与技术选型解读1.1 SpringBoot2不是SpringBoot3这是一个刻意的稳定选择如果你经常逛开源社区会发现2024年以后新出的项目普遍开始用SpringBoot3和JDK17了这套系统还在用SpringBoot2是不是过时了我的看法是不算反而在毕业设计、课程设计这类场景下是更稳妥的选择。SpringBoot2生态非常成熟网上资料多到看不完遇到问题随便一搜就有答案依赖版本之间的兼容性也被大量项目踩过基本不会出现“某个依赖拉不下来”这种浪费时间的处境。具体到SpringBoot版本一般用的是2.5或2.6系列搭配JDK8进行开发这两个组合在内存占用和启动速度上非常友好。一个普通的课表管理系统后端服务启动后占用大概在200MB左右哪怕是配置一般的笔记本跑IDEAMySQL前端DevServer也完全不卡顿。很多同学纠结要不要直接上SpringBoot3我建议看你的运行环境——如果是实验室老电脑、机房机器或者学校要求统一JDK8环境SpringBoot2绝对是省心选如果导师要求新、想体验新特性再考虑SpringBoot3但这牵扯到JDK17、更严格的依赖兼容性排查改造成本不是一点点。SpringBoot2的核心价值在于几件套内嵌Tomcat免去部署麻烦、自动配置把繁琐的Bean装配压到最低、Actuator提供监控端点、和MyBatis-Plus的整合非常丝滑。我用图形化的方式打个比方SpringBoot2就像一套精装修好的房子你拎包入住只需要买点家具写业务代码而传统的SSH框架是你得从砌墙开始装修。1.2 Vue3组合式API带来的前端开发体验变化前端从Vue2换到Vue3最大的变化不是性能那点提升而是Composition API对代码组织方式的重构。课表管理系统的前端如果用Vue2的Options API写数据和逻辑分散在data、methods、computed、watch各个选项里当一个页面逻辑复杂的时候比如课表网格渲染周次切换弹窗排课冲突提示你会在各个选项之间来回跳代码的可读性会迅速恶化。这套系统用Vue3改写后代码组织方式变成了按功能聚合排课相关的响应式数据、调用接口的方法、计算属性全都集中在script setup里。比如你看一段典型的排课逻辑它会用ref或reactive定义一个课表数据对象用onMounted在页面加载时拉取当前周的课程数据再定义一个loadScheduleByWeek函数专门去请求后端接口。整个过程线性的像在读一个操作手册而不像Vue2那样需要靠命名约定来维持秩序。除了写法上的变化Vue3配合Vite带来的开发体验升级也很明显——开发服务器启动秒开、热更新快到几乎没有感知改一行代码浏览器立刻刷新这在频繁调整课表单元格样式、反复试错的时候太舒服了。这套源码如果是用Vite搭建的工程化项目你还能看到/路径别名、环境变量区分Dev/Prod、ESLint配置这些基础设施这些都是Vue2时代用vue-cli做不到这么清爽的。1.3 MyBatis-Plus对常规CRUD的降维打击MyBatis-Plus能火原因太现实了——写繁琐的增删改查SQL是后端开发里最机械、最没意思的部分。一张课程表要有新增、删除、修改、分页查询、条件查询五个接口用原生MyBatis你得写5个Mapper方法加5段XML SQL用MyBatis-Plus你只需要继承一个BaseMapperCourse接口单表CRUD直接原地毕业。这套系统里MyBatis-Plus承担的不是花活而是最基础的数据访问层。比如课表系统的几个核心实体——课程信息、教师信息、教室信息、排课记录每一张表的CRUD都可以通过内置方法来搞定。当你添加课程信息时一条insert删除课程一条deleteById分页查询排课记录一条selectPage。在没有特别复杂的多表关联查询需求的前提下MyBatis-Plus把代码量压缩了至少一半。更妙的是MyBatis-Plus的QueryWrapper和LambdaQueryWrapper把拼接SQL这种容易出错的事情变成了链式调用。举一个实际场景查询“某个教师在某学期某周的所有排课记录”用LambdaQueryWrapper写就是LambdaQueryWrapperSchedule wrapper Wrappers.lambdaQuery(); wrapper.eq(Schedule::getTeacherId, teacherId) .eq(Schedule::getSemesterId, semesterId) .apply(FIND_IN_SET({0}, week_list), weekNo) .orderByAsc(Schedule::getDayOfWeek) .orderByAsc(Schedule::getSectionStart);这种写法好维护在两点一是字段名用方法引用编译期就能发现写错的字段二是条件逻辑是自己拼接的动态增加减少非常灵活。课表查询天然依赖各种动态条件——按教师查、按教室查、按星期查、按课程查用QueryWrapper一套组合拳下来几乎不用写一行XML。1.4 MySQL8.0作为数据底座的优势MySQL8.0相比之前的5.7版本在功能和性能上都有明显升级。对课表系统来说最能用上的是窗口函数和公用表表达式CTE。假如你想在SQL层面直接查出“每个教师的周排课节数排行”用MySQL8.0的窗口函数可以这么写SELECT teacher_name, SUM(credit_hours) AS total_hours, RANK() OVER (ORDER BY SUM(credit_hours) DESC) AS rank_no FROM schedule GROUP BY teacher_name;这在5.7版本里是要靠临时表或者子查询绕路的8.0直接一套带走。虽然这种统计需求在课表系统里不是核心但以后你扩展成绩统计、课时统计功能时8.0的窗口函数能帮你省不少事。另一个实际利好是JSON类型的完善和默认字符集utf8mb4MySQL8.0默认字符集就是utf8mb4存生僻字和emoji都不存在乱码问题对前端显示各种特殊符号很友好。给数据库做初始化配置的时候记得统一设为utf8mb4和utf8mb4_general_ci否则后患无穷。2. 数据模型设计好课表系统就成功了一半2.1 核心表结构与字段设计思路课表系统的数据模型绕不开几件事课程在哪、老师在哪、教室在哪、什么时间上。围绕这几个问题核心表就清晰了——课程表、教师表、教室表、班级/学生表、学期表、排课记录表。前四张是基础资料表排课记录表是业务核心表学期表起到一个时间维度的统领作用。以排课表为例实际设计时字段大致是这些字段名类型说明idbigint主键semester_idbigint所属学期course_idbigint关联课程teacher_idbigint关联教师class_idbigint关联班级classroom_idbigint关联教室day_of_weektinyint星期几1-7section_starttinyint开始节次section_endtinyint结束节次week_starttinyint开始周次week_endtinyint结束周次week_typevarchar全周/单周/双周statustinyint状态启用或停用这个表的亮点在于“周次”字段没有被设计成逗号分隔的列表也没有做成一行一次课的展开形式而是用week_start、week_end加上week_type来描述一段连续区间。比如高数课第1周到第16周每周都上那就是week_start1, week_end16, week_typeALL如果第2周到第18周只上单周那就是week_start2, week_end18, week_typeSINGLE。这种设计在查询和展示时响应快但给冲突检测增加了算法复杂度后面我会专门讲。2.2 角色权限设计的三种常见方案课表管理系统一般有管理员、教师、学生三种角色权限控制的复杂程度取决于你想做到多细。这套源码里我见过最典型的做法是用Spring Security做登录认证配合自定义的权限注解来控制接口访问级别。管理员可以执行所有操作——增删改查课程、安排排课、管理用户教师登录后只能看到自己和任课班级的课表学生登录后只能查询课表和选课结果。如果你拿到的版本用的是JWT拦截器也别慌效果是一样的。用JWT的好处是后端不保存会话状态前端每次请求在Header里带上Authorization: Bearer token后端通过拦截器统一解析token把用户信息塞到ThreadLocal或请求上下文的工具类里后续的业务代码直接取用。对课表系统这种并发量不高的场景JWT的实现更轻量排查问题也方便。权限的粒度上最常见的是接口级别保证筛选无权限的用户。比如给排课相关的接口加上PreAuthorize(hasRole(ADMIN))这样的注解教师和学生看了也调不通。有些系统会在前端做菜单级别的权限控制——比如根据登录用户的角色动态过滤菜单栏让教师看不到“用户管理”这个菜单项。前端做过滤只是改善体验真正的安全防线必须放在后端接口上这一点项目文档里如果提了要当重点经验记住。2.3 排课冲突检测这套系统的技术难点所在课表系统的真正技术难点绝对不是CRUD而是排课冲突检测。当管理员想把一门课安排到某间教室时系统必须回答三个问题这个教室这个时间段有没有被占用这个老师这个时间段有没有课这个班级这个时间段有没有课这三个问题本质上是同一个时间重叠判断问题。时间重叠的判断不能想当然地写start_time 课程的end_time之类而是要仔细推导。两个时间段存在重叠的条件是新排课的开始节次小于等于已有排课的结束节次且新排课的结束节次大于等于已有排课的开始节次。套到SQL里查询某个教室某个时间段是否被占用核心条件是这样一段SELECT COUNT(*) FROM schedule WHERE classroom_id #{classroomId} AND semester_id #{semesterId} AND day_of_week #{dayOfWeek} AND ( #{sectionStart} section_end AND #{sectionEnd} section_start )这只是处理了课表冲突中最基础的节次重叠。一旦引入单双周和周次区间判断条件还得加上周次维度的交叉。比如某教室周一3-4节被单周的体育课占用你要排双周的数学课虽然在星期和节次上重叠但因为周次类型不同其实并不冲突。这个判断在SQL里也能写但逻辑会很长很拗口。一个更直观的做法是先把候选时间段内所有已存在的排课记录查出来在Java代码里做周次相交判断逻辑更清晰也更好调试。我在真正实现这个功能的时候遇到的最麻烦的问题不是算法逻辑而是“如何让管理员看懂为什么冲突”。单纯弹一个“排课冲突”的提示用户会一头雾水。好的做法是冲突时返回冲突的具体原因比如“该教室在周一3-4节已被《计算机网络》占用任课教师为张三”前端把这个原因用红色弹窗展示出来。这个细节做得好不好直接影响系统给人的专业感也是答辩时老师比较感兴趣的点。3. 后端架构设计与核心接口实现3.1 分层结构Controller、Service、Mapper的职责边界拿到这份源码后第一件事是看包结构。标准的三层架构会分成controller、service、mapper或者叫dao三个包业务逻辑按照“Controller接收请求参数→Service处理业务规则→Mapper访问数据库”的方向流动。这种分层是有现实意义的如果前端A页面需要取课表同时还需要顺带获取课程名、教师名和教室名正确的做法不是在Controller里写一大段组装逻辑而是让Service层去调用多个Mapper或ServiceImpl方法把组装好的数据统一返回给Controller。我见过不少学生写代码时会跳过分层直接在Controller里注入Mapper几行代码搞定一个接口看起来省事但后患无穷。比如后来要加权限控制、加缓存、加日志你会发现所有Controller都混着一堆重复代码改一处要牵连好几处。遵循分层的代码权限注解、日志切面可以直接作用在Service层或Controller层清清楚楚。判断这套源码的成色如何打开它的Service实现类看看方法体里是只有一行Mapper调用还是有真正的业务逻辑——有业务规则、有异常处理、有事务注解的才算值得细看。事务在这里就有一个典型场景新增排课记录时至少要插入一条schedule记录同时可能要更新该班级的已排课时数统计。两步操作要么都成功、要么都失败对Service方法打上Transactional事务注解即可否则可能会出现插入了排课记录但统计数字没变的脏状态。这是课表系统细节里比较容易被忽略的。3.2 用MyBatis-Plus打造通用CRUD服务这套源码比较有意思的一个设计是多模块项目里面有一个独立的公共模块专门封装了基于MyBatis-Plus的通用CRUD服务。简单讲这个通用服务往大了说可以让后续所有业务模块都直接复用一套增删改查接口——你只需要定义实体类不用写Controller层的冗长重复代码就能对外暴露一套标准的RESTful接口。拿课表系统来举例课程、教师、教室这些基础资源天然就是标准CRUD结构用通用服务处理非常合适。假设我定义了一个课程实体CourseDO通用服务可以自动生成GET /course/page、GET /course/{id}、POST /course、PUT /course、DELETE /course/{id}这些接口。而排课表这种带着复杂业务规则的则不要套用通用服务需要单独写Service来处理冲突检测、周次解析等逻辑。这个设计思路非常实用保证了“日常操作省代码、核心逻辑有深度”。前端这边一般会有封装好的Axios请求工具GET、POST、PUT、DELETE四个方法都给你准备好统一配置了baseURL和请求拦截器自动附加token。实际调用时前端只需要写类似http.post(/schedule/add, scheduleForm)这样的代码。这种通用CRUD加精确业务接口的搭配方式其实就是企业里常说的“低代码能力复用”在一个课设项目里看到这样的设计只能说源码作者确实有工程经验。3.3 JWT登录认证的实现与拦截器配置登录认证模块我会详细说说因为这是最容易被忽略但又在答辩时最容易被追问的环节。系统一般会提供初始管理员账号比如admin/admin123。登录流程大致是前端提交用户名密码到/auth/login后端验密通过后生成JWT并返回前端存到localStorage或sessionStorage之后每次请求在Axios拦截器里自动带上。JWT本身由三部分组成Header、Payload、Signature。Header声明了签名算法Payload里放用户ID、用户名、角色ID、过期时间等信息Signature用服务端密钥签名防止令牌被篡改。登录认证的代码如下注意这个签名密钥在真实项目中要放到配置中心或环境变量不要硬编码在源码里String token Jwts.builder() .setSubject(username) .claim(userId, user.getId()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) .signWith(secretKey) .compact();登录成功后返回的token前端会在请求拦截器里塞进Header后端拦截器统一校验。如果token过期或非法拦截器直接返回401状态码前端Axios拦截器统一弹出“登录已过期请重新登录”并跳到登录页。这个小细节特别重要如果没有统一处理每个接口各自报错用户体验会非常混乱。关于密码安全不建议明文存储虽然很多课设源码这么干正规一点的做法是MD5加盐或者用BCrypt加密。MyBatis-Plus本身没有这个能力但Spring Security自带的BCryptPasswordEncoder一行代码就能完成。如果拿到的源码里没有做加密我建议你至少加上BCrypt答辩时提到这一点会显得你的安全意识很好。4. 前端Vue3实现课表展示与排课交互4.1 课表网格组件实现思路前端核心难点都集中在课表展示上——一个7列周一到周日、最多12行第1节到第12节的网格。如果按照时间网格的方式渲染基础布局是这样第一列是节次后面六到七列是星期每个单元格里展示课程名、教师名、教室名。如果课程要跨节次课表里还要实现合并单元格的效果。用Vue3实现这个大多数人会用一个二维数组来维护课表数据示例如下const weekSchedule ref([]); // weekSchedule[row][col] { // courseName: 高等数学, // teacherName: 张老师, // classroomName: A302, // rowspan: 2, // color: #409EFF // }渲染的时候通过v-for遍历二维数组遇到rowspan 1的单元格就用td的rowspan属性跨行显示。麻烦的地方在于课表数据从后端返回时通常是按排课记录返回的每条记录包含dayOfWeek、sectionStart、sectionEnd前端要自己把它转换成二维网格结构。这段转换逻辑是整个前端最值得仔细看的部分因为是做课表系统绕不开的算法转换。我的建议是在前端单独建一个scheduleConvert.js工具模块把后端排课记录数组映射成网格对象用computed属性做缓存。如果排课记录变化网格自动重新计算这个思路清晰且容易扩展。调整节次或周次时二维数组只需要重新初始化再添加数据别忘了要先清空数组引用因为ref的赋值需要.value。4.2 周次切换和学期筛选的实现高校课表有个特点不同周课表不一样。第1周有高数第3周高数停了上实验课看起来像动态变化其实是周次维度的数据筛选。前端在做周次切换时最直接的办法是每次周次变化就重新请求后端接口让后端返回该周的有效课表。这样对前端最简单但对后端接口的查询逻辑有要求——要根据week_start、week_end、week_type计算某一周是否含有该节课。判断某个周是否在排课记录的有效范围内Java代码核心思路是这样public boolean isWeekInRange(Integer weekNo, Schedule s) { if (weekNo s.getWeekStart() || weekNo s.getWeekEnd()) { return false; } if (ALL.equals(s.getWeekType())) { return true; } if (SINGLE.equals(s.getWeekType())) { return weekNo % 2 1; } if (DOUBLE.equals(s.getWeekType())) { return weekNo % 2 0; } return false; }前后端都实现周次判断逻辑在排课系统中很常见。你要注意前后端判断逻辑保持一致否则会出现前端高亮显示有课但点进去后接口返回空的情况。比较稳妥的工程做法是将周次判断作为后端查询参数处理前端不自己计算但课表预览功能为了方便也可以允许前端先基于已加载的数据做本地过滤预览不请求后端正式查询再走后端。4.3 弹窗表单完成排课交互与冲突提示排课操作是管理员的日常核心动作交互上一般通过点击某个空白单元格或右下角的“添加排课”按钮打开Dialog弹窗。弹窗里会包含几个关键表单项课程下拉选择、教师下拉选择、班级下拉选择、教室下拉选择、星期选择、开始节次、结束节次、以及周次设置起始周、结束周、单双周类型。这里有一个前端体验细节值得注意教师下拉选择了某个教师之后最好触发联查显示该教师已排课的时间段让管理员直观看到哪些时间段是已经占掉的。同样教室下拉选择之后也把该教室的占用时段列出来。这个过程虽然只是一个前端展示但能把冲突提前暴露在操作之前真是个特别实用的小功能。真正提交的时候后端会再次做冲突检测双重校验。前端做这个联查会多几次接口请求但对课表管理系统这种并发量不高的场景完全没影响。弹窗里还需要处理一个联动逻辑当教室选项变化时要判断新选的教室是否跟已选教师或班级的时间冲突。如果有冲突需要用醒目的红色提示并阻止提交。我在实际开发中会把冲突检查的逻辑抽成一个公共函数checkConflict(payload)来复用区分开教室冲突、教师冲突、班级冲突三种类型分别返回错误信息而不是笼统地告诉用户“存在冲突”了事。5. 从零到一跑通环境的完整实操过程5.1 本机安装MySQL8.0与初始化数据库拿到源码第一件事就是把数据库环境搭起来。MySQL8.0的安装方式个体差异很大Windows下有Installer安装包Linux下可能需要配置yum源或apt源。不管走哪条路安装完都要确保几点版本号是8.0.x而不是5.7字符集选择utf8mb4端口默认3306别被改了如果是Windows服务要注册成Windows服务以便开机自启。启动MySQL后用root进入命令行先创建一个业务专用的数据库账号再把源码包里的数据库脚本导入。通常源码目录下会有一个sql/文件夹里面的初始化脚本可能包含建库、建表、插入初始数据等步骤mysql -u root -p CREATE DATABASE IF NOT EXISTS schedule_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE schedule_db; SOURCE /path/to/sql/init.sql;如果该源码用的是Spring的数据库自动初始化机制schema.sql与data.sql那么你只需要在application.yml里配置好连接信息应用启动时会自动建表并插入数据。我更推荐这种做法因为部署时少一个手动导入步骤对环境的侵入小。但要注意设置spring.sql.init.modealways同时确保连接账号有建表权限。5.2 后端项目启动与常见配置调整后端源码导入IDE后要重点检查application.yml或application-dev.yml里的配置项。下面是典型的配置片段每个项目大同小异你需要修改的通常是数据源连接、Redis地址如果用了缓存、以及日志级别server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/schedule_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: schedule_user password: schedule_pass driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.schedule.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplserverTimezoneAsia/Shanghai这个参数特别容易写漏不写的话会碰到“无法解析日期时间字符串”之类的报错。MySQL8.0的驱动类名也变了是com.mysql.cj.jdbc.Driver而不是老的com.mysql.jdbc.Driver这个问题出在pom.xml里依赖的版本不对时。allowPublicKeyRetrievaltrue也是MySQL8.0连接时经常要加的参数不然在某些情况下会报Public Key Retrieval is not allowed这是新版本认证机制导致的。启动后端之后可以用浏览器直接访问Swagger或者Knife4j的地址如果有引入的话比如http://localhost:8080/doc.html检查接口是否都注册成功也可以直接在文档页面里测试登录接口并获取token。沒有的话就先用POSTMAN测试登录后请求其它接口确认用户认证链路是通的。5.3 前端项目安装、启动与接口联调前端工程一般使用Vite构建启动方式非常简单npm install npm run dev默认端口一般是5173或3000启动后打开浏览器访问Vite给的地址。第一次跑起来最容易遇到的问题是跨域请求失败或者请求404这通常是因为Vite没有配置开发代理。正确的做法是在vite.config.js里配置代理把前端的接口请求转发到后端export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这个代理配置的关键点是前端的Axios统一用/api开头请求到了Vite的DevServer后再转给后端的8080端口changeOrigin设置为true保证后端拿到的请求头Host是后端地址而不是前端的。如果不加代理前端直接请求http://localhost:8080不仅会有跨域问题还会把后端接口地址硬编码在代码里非常不利于后续部署。如果生产环境部署后端就要想办法处理跨域要么让后端配置CorsFilter要么用Nginx做反向代理把前后端放在同一个域名下。课设演示阶段用开发代理就够了如果要打包部署建议用Nginx托管前端静态文件把/api路径反代到后端服务。5.4 Docker方式快速搭建MySQL8.0备选方案本机不打算装MySQL或者感觉安装过程麻烦的时候Docker是很好的备选。一条命令把MySQL8.0拉起来docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEschedule_db \ -e TZAsia/Shanghai \ -v mysql_data:/var/lib/mysql \ mysql:8.0注意几个关键点一是时区环境变量记得设成Asia/Shanghai不然Spring连接时因为时区差异可能出现时间偏移二是映射了数据卷容器删了数据还在不会一朝回到解放前三是如果用root密码登录连接串里别忘加allowPublicKeyRetrievaltrue。Docker这种方式极其适合想在Linux服务器上快速搭一套演示环境的场景。用Docker跑MySQL8.0还有一个好处就是可以随意修改端口映射比如本机3306已被别的老版本MySQL占用你可以映射成3307这样多套数据库环境共存互不干扰。在调试课表系统的时候我经常会在一个Docker容器里跑MySQL8、另一个跑Redis、再加一个后端服务整个环境干净利落排障方便。6. 真枪实弹踩坑记录常见问题与排查手段6.1 MySQL8.0连接与驱动相关报错课表系统部署阶段最高频的报错几乎全是围绕数据库连接展开的。常见的有这么几个一个是Public Key Retrieval is not allowed。这个报错来自MySQL8.0使用caching_sha2_password认证插件某些客户端第一次连接时拿不到公钥所以需要在JDBC连接串上加上allowPublicKeyRetrievaltrue。另一个是The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这是典型的时区乱码问题跟环境语言编码有关解决办法是连接串加serverTimezoneAsia/Shanghai。还有一个很经典的是Access denied for user xxxlocalhost多半是因为数据库里压根没有这个账号或者账号密码不对还有可能是账号只允许特定host登录。遇到这种问题不要慌箪查看授权就是。驱动依赖的版本也可能是坑。SpringBoot2.5或2.6内置的MySQL驱动版本通常能兼容MySQL8.0但如果pom里手动指定了过老的8.0.11版本就可能出现驱动类找不到或协议版本不匹配的怪问题。我一般会直接使用SpringBoot依赖管理指定的版本除非有特殊情况才会手动覆盖。6.2 Vue3开发中常见的前端遗留习惯问题很多同学第一次上手Vue3时会按Vue2的习惯写代码导致各种报错和奇怪表现。最典型的一个是在script setup里用this。Vue3的选项式API保留着this但组合式API中这种写法是行不通的访问ref定义的变量必须用变量名.value访问reactive对象的属性可以直接obj.xxx但绝不能去用this.xxx拿到的肯定是undefined。第二个高频问题是响应式丢失。有人写过这样的代码const scheduleData reactive({ week1: [], week2: [] }); const { week1, week2 } scheduleData;解构出来的week1和week2是普通变量已经失去了响应式能力。正确做法是用toRefs或者直接通过scheduleData.week1引用。这个问题特别隐蔽因为这代码看起来没有任何报错但页面上就是不会主动更新。第三个问题是生命周期函数名的变化。beforeDestroy在Vue3里变成了beforeUnmountdestroyed变成了unmounted如果你按Vue2的老名字写控制台会提示生命周期钩子未找到后面如果放在里面写清理定时器或解绑事件的逻辑就会因为没在正确时机被调用而出莫名问题。6.3 排课冲突检测中的边界场景冲突检测逻辑乍一看很简单真正写的时候你会发现各种边界场景层出不穷。比如说单周、双周、全周三种周次类型混排某门课第1周开始第8周结束另一门课第5周开始第16周结束课与课之间可能允许连上第1-2节连着上完后第3-4节接着上另一个人但节次不能交叉重复。这些边界情况不做特殊处理就很容易出现误判或漏判。我建议做一个本地的“排课冲突测试清单”至少有这几条必须测到同一教室、同一周次、同一节次不同教师和不同课程应该冲突同一教师、同一周次、同一节次不同教室和不同课程应该冲突同一班级、同一周次、同一节次不同教室和不同课程应该冲突单双周不同的两门课即使星期和节次完全重叠也不应冲突时间上首尾相接的课程比如第1-2节和第2-3节按教学实际这是冲突的因为第2节重叠了不要把判断写成相邻就合法测试方式可以在浏览器网络面板里直接看接口响应也可以在Swagger里反复调接口验证。把这套测试用例整理好本身就是答辩时的加分项——展示出你不仅会写代码还懂如何系统性地验证正确性。6.4 课表展示中的样式与数据格式问题前端课表展示容易出的问题主要集中在样式错乱和数据格式不匹配。Common的情况是课表网格合并单元格后因为表格结构计算不对出现了单元格数量对不上、错位、甚至整个表格参差不齐。解决的办法多半是重新理清二维数组的构建逻辑——先初始化纯空白的网格再按每周的天数和节次数初始化最后填数据时带上rowspan和colspan信息渲染时自动合并。另一个格式问题经常出现在后端返回的时间字段上。Java8的LocalDateTime序列化成JSON的时候如果不做格式化处理前端看到的可能是2025-06-10T08:00:00而不是2025-06-10 08:00:00。最简单的统一处理方式是在application.yml里配置Jackson的日期格式或者在实体类的时间字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)。课表系统里如果用到了排课开始时间和结束时间这个坑非常值得留意。前端如果遇到接口数据返回到表格显示为[object Object]那多半是没搞清楚数据结构直接渲染了对象本身。这时候要到浏览器的Network里看接口实际的JSON结构再去调整Vue模板里的字段引用路径不要瞎蒙。7. 关于这套源码的后续扩展建议课表管理系统这种项目完成基本功能远远不是终点。按照我自己的习惯拿到源码之后一定会做这么几件事第一把账号密码的存储方式改成BCrypt加密绝不明文存储第二给登录接口增加验证码校验哪怕是简单的算术验证码能挡住不少无聊的扫描第三增加导入导出功能把课表Excel一键导入、排课结果一键导出这在真实教务场景中属于刚需也是课设里很容易出彩的扩展点。如果你想把这个项目从“课设水平”拔高到“准生产水平”还有几个方向值得深挖。一个是Redis缓存所有课表查询接口加一层缓存设置合理的过期时间课表变化时主动清除相关缓存这能让并发能力有质的提升。另一个是操作日志排课、改课、调课这些关键操作记录审计日志谁在什么时间做了什么修改都要有据可查。还有一个是消息提醒排课成功后自动把课表推送通知给相关教师用WebSocket或者邮件都可以实现。我对这套源码的整体评价是课程设计和毕业设计的经典模板架构清晰、技术栈主流扩展起来不费劲。但要真正变成一个有个人印记的作品关键还是看你在基础之上做了多少自己的思考和改造。课表系统的核心从来不在CRUD在于冲突检测、数据可视化、复杂查询这些细节的打磨。把这些点做透了哪怕大框架是拿来的答辩时也完全能讲出深度。最后说点实在的我在跑这套系统时最大的体会是后端的排课冲突检测和前端课表网格转换才是真正需要耐心啃的硬骨头也是整个项目最有技术含量、最能打动评审老师的地方。如果你刚拿到这套源码我建议不要急着改功能先把这两块代码完整读一遍甚至自己动手重新实现一遍读懂了它们这套系统就真正变成你的了。