项目概述:这套课表管理系统的定位与价值
先说我拿到这个标题的第一反应:SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0,这一套组合在 Java Web 课程设计、毕业设计、企业内部小工具开发里,属于非常典型又耐打的技术栈。课表管理系统听起来简单,实际上它横跨了后端 REST API 设计、前端动态表格渲染、数据库表结构设计、权限控制、导入导出这几个高频痛点,几乎把 Java Web 开发的核心环节全部覆盖了一遍。
这套系统适合谁?第一种是正在做课程设计或毕业设计的同学,第二种是刚入行想找一个完整项目练手的前端或后端工程师,第三种是想快速搭一套内部排课工具的业务开发。它的核心价值不在于“课表”这个业务本身,而在于它把一套主流技术栈的骨架搭得足够干净,文档齐全,能让你在最短时间内跑通从数据库到前端页面的完整链路。
我拿到源码后先做了一遍整体梳理,这套系统的结构不复杂但五脏俱全:后端基于 SpringBoot2 做接口层,MyBatis-Plus 负责持久层操作,MySQL8.0 存数据;前端用 Vue3 配合主流 UI 组件库做后台管理界面。业务模块覆盖了课程信息、教师信息、班级信息、课表排布、调课申请这些常见场景。下面我从技术选型到实际运行,把每一个关键环节的底层逻辑和实操经验拆开讲。
1. 技术选型逻辑:为什么是这套组合
1.1 后端框架选型的核心考量
SpringBoot2 在这个项目里几乎是必然选择。它对 Spring 生态做了大量的自动配置收敛,省去了 XML 配置文件的繁琐工作。更重要的是 SpringBoot2 的内嵌 Tomcat 机制让部署变得非常简单——打一个 jar 包直接跑,不需要单独装 Tomcat、配 server.xml,这对课程设计演示和中小型内部系统来说体验极好。
我自己试过从 SpringBoot1.x 迁移到 2.x,最大的体感差异在两点:一是配置项大幅简化,二是第三方组件的版本兼容性更强。这套源码用的是 SpringBoot2,如果读者想换成 SpringBoot3,需要额外注意 javax 到 jakarta 命名空间的迁移问题,这个坑不小,建议课程设计阶段不要轻易换版本,先跑通再说。
1.2 MyBatis-Plus 解决什么问题
MyBatis-Plus 在这个项目里的作用被我称为“CRUD 加速器”。传统的 MyBatis 需要为每个实体写 Mapper 接口、XML 文件、SQL 语句,即使是一个最简单的单表查询也要写不少样板代码。MyBatis-Plus 直接内置了通用 Mapper 和通用 Service,单表增删改查几乎不用写 SQL。
举个例子,课表管理里最频繁的操作是“查询某教师某天的课程”,如果用原生 MyBatis,你得手写 SQL 还要注意参数映射;用 MyBatis-Plus,一句lambdaQuery().eq(Teacher::getId, id).eq(DayOfWeek::getCode, code).list()就搞定了。这套源码里的所有单表操作基本都走这条路子,效率提升非常明显。
还有一点值得提:MyBatis-Plus 的分页插件配置。源码里应该已经配好了PaginationInnerInterceptor,这是很多初学者容易漏掉的部分——不配分页插件,Page对象只会查出全表数据,分页逻辑完全失效,这是一个非常实际的坑。
1.3 Vue3 相比 Vue2 的优势在哪里
前端选 Vue3 是顺应趋势的做法。Vue3 的 Composition API 把逻辑复用能力提升了一大截,同样的功能,Vue2 里你可能要用 mixin 混入,代码一多根本不知道数据从哪里来;Vue3 里全部收敛到 setup 函数中,数据流清晰可追踪。
这套源码里的前端页面应该大量使用了 Composition API 的写法。看代码的时候你会发现,课表页面的数据加载、筛选、展示逻辑都被封装成了独立的ref和computed,比 Vue2 时代的 data 选项式写法直观得多。另外 Vue3 对 TypeScript 的支持也是原生级别的,虽然这套源码可能没用 TS,但如果你后续想加类型安全层,Vue3 的迁移成本要远低于 Vue2。
1.4 MySQL8.0 带来哪些实际影响
MySQL8.0 相比 5.7 最大的变化是默认字符集变成了 utf8mb4,对 emoji 和中文支持更友好。同时 8.0 引入的窗口函数、CTE(公共表表达式)等特性,让复杂报表查询写起来更顺手。课表管理这种业务虽然用不太上这些高级特性,但 8.0 的默认事务隔离级别和连接性能确实更稳。
安装 MySQL8.0 时我踩过一个坑:8.0 的默认认证插件是caching_sha2_password,某些老版本的数据库连接工具和 JDBC 驱动不兼容,会报认证错误。解决方案要么把认证插件改成mysql_native_password,要么确保 JDBC 驱动版本够新。这套源码用的 MySQL 驱动只要在 8.0.x 以上基本没问题。
2. 数据库设计拆解:课表系统的核心表结构
2.1 表结构设计思路
课表管理系统的核心难点不在字段多,而在于“排课”这个动作的抽象。我看了这套源码的建表 SQL,整体遵循了“学生、教师、课程、班级、时间槽”五个基础维度拆分的思路。
拿课程表来说,它不只是存一个课程名称和学分,还要关联教师 ID、班级 ID、上课时间(星期几 + 第几节)、上课地点。这种设计本质上是一个“多对多关系的有意扁平化”——把课程和教师、班级的关联直接冗余到课程表里,牺牲了一点范式,换来了查询效率。对于课表这种高频读、低频写的业务场景,这种妥协是完全值得的。
2.2 核心表字段的逐项说明
我梳理了一下这套系统最核心的几张表的字段设计:
| 表名 | 关键字段 | 设计目的 |
|---|---|---|
| 课程表 | id, course_name, credit, teacher_id, class_id, week_day, section, location | 核心业务表,存储排课信息 |
| 教师表 | id, teacher_name, department, title, phone | 基础数据,关联课程表 |
| 班级表 | id, class_name, grade, major | 基础数据,关联课程表 |
| 用户表 | id, username, password, role, status | 认证与权限控制 |
| 调课申请表 | id, course_id, old_time, new_time, reason, status | 业务流转,状态审批 |
这里面比较值得关注的是“调课申请”这张表。它说明这套系统不只是单纯的课表展示,还有业务流程——学生或教师发起调课申请,管理员审批,审批通过后更新课程表。这种设计让学生管理系统不只是 CRUD 演示,而是真正具备业务闭环的逻辑。
2.3 外键与索引设计的避坑经验
数据库设计里我特别想强调一点:不要滥用外键。这套源码里,课程表关联教师和班级,很多初学者会下意识加外键约束,但实际上在互联网级的业务系统里,外键约束会严重影响写入性能和后续分库分表。正确做法是在业务层面控制关联关系,数据库层面只建立普通索引。
我看了这套表结构的设计,走的就是“逻辑外键 + 物理索引”的路线——业务表里存 teacher_id 但不设真正的 FOREIGN KEY,需要联表查询时用 JOIN 或 MyBatis-Plus 的关联查询解决。这个经验对课程设计阶段尤其重要,因为很多评委老师会关注数据库设计的规范性和合理性,你如果能说清楚“为什么不用外键”,反而是加分项。
2.4 初始化数据的处理技巧
源码的 SQL 脚本里应该包含建库、建表、插入初始数据的完整流程。我建议部署的时候不要图省事直接执行一遍就完事,而是按这个顺序操作:
- 先执行建库语句,数据库名统一(比如 schedule_db)
- 再执行建表语句,按依赖关系排序(先建无外键依赖的基础表,再建业务表)
- 最后执行初始数据脚本,填入测试账号和示例课程
- 用
SELECT语句抽查几条核心数据,确认编码、自增主键等没有问题
MySQL8.0 在 Windows 下执行 SQL 脚本时如果遇到编码问题,可以在执行前先执行SET NAMES utf8mb4;。这个坑在 Linux 下少见,Windows 下经常遇到。
3. 后端核心实现:SpringBoot2 接口层到底怎么搭
3.1 项目目录结构的推荐姿势
后端代码的目录结构决定了后续维护的体验。我看这套源码的分层比较标准,大致是:
src/main/java ├── controller # 控制器层,接收前端请求 ├── service # 业务逻辑层 ├── mapper # 数据访问层 ├── entity # 实体类 ├── config # 配置类 └── common # 通用返回结果、工具类等这种分层结构的好处是职责清晰:controller 只负责参数接收和结果返回,不写业务逻辑;service 层做业务判断和事务控制;mapper 层只做数据交互。实际开发中,很多同学把业务逻辑写在 controller 里,接口越写越肥,后面改一个需求要动好几个地方。我个人的经验是:如果 controller 里的一个方法超过 30 行还在写业务,就该考虑拆到 service 层了。
3.2 RESTful API 设计与统一返回体
这套源码的接口路径设计走的是 RESTful 风格。比如课表的接口大概是:
GET /api/courses 查询课程列表(支持分页和条件筛选) GET /api/courses/{id} 根据 ID 查询课程详情 POST /api/courses 新增课程 PUT /api/courses/{id} 更新课程信息 DELETE /api/courses/{id} 删除课程 POST /api/courses/adjust 提交调课申请有个细节值得学习:源码里应该有统一的Result返回体——包含 code、message、data 三个字段。这样做的好处是所有接口返回结构一致,前端不用为每个接口单独处理响应格式,Axios 拦截器里统一判断code是否为 200 就能处理绝大多数异常情况。
我给读者一个建议:就算你说不清楚什么是“优雅的 API 设计”,也一定要在课程设计说明里写清楚“统一返回结构”这一点。这是面试官或评委很容易追问的细节,也是区分“会写接口”和“会设计接口”的分水岭。
3.3 MyBatis-Plus 条件构造器的使用要点
MyBatis-Plus 的条件构造器(Wrapper)是这套源码里用的最频繁的工具。我简单讲一下最常用的几种写法:
// 查询某教师的课程列表 LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Course::getTeacherId, teacherId); List<Course> list = courseMapper.selectList(wrapper);// 分页查询 + 条件筛选 Page<Course> page = new Page<>(current, size); LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(courseName), Course::getCourseName, courseName); wrapper.eq(teacherId != null, Course::getTeacherId, teacherId); Page<Course> result = courseMapper.selectPage(page, wrapper);这里有一个我踩过的坑:eq的第二个参数如果是 null,MyBatis-Plus 默认会生成column = null的条件,导致查询结果异常。正确的姿势是用condition重载方法:当参数不为 null 时才拼接查询条件。源码如果写得规范,应该已经处理了这个问题,但你不妨自己检查一遍。
3.4 事务控制的正确姿势
排课操作有时候涉及多张表的写入。比如新增一门课程,要同时写入课程表、更新教师课时统计、写入操作日志。这时候必须保证要么全部成功、要么全部回滚,不能出现课程加上了但日志没写的情况。
SpringBoot 里最直接的做法是在 service 方法上标注@Transactional注解:
@Transactional(rollbackFor = Exception.class) public void addCourseWithLog(Course course, OperationLog log) { courseMapper.insert(course); logMapper.insert(log); }rollbackFor = Exception.class这个参数不能省。默认情况下 Spring 只对 RuntimeException 回滚,如果你在方法里抛出的是受检异常(比如 IOException),事务不会回滚,数据就出现不一致了。这个细节我在给很多人 review 代码时都会特别指出来。
3.5 登录认证与权限控制方案
课表管理系统里,不同角色的需求差异很大:学生看课表、教师管理自己的课程、管理员管理所有数据和调课审批。这要求系统有最基本的登录认证和权限控制。
这套源码的认证方式我猜测是基于 JWT 的 Token 方案,基本原理是:
- 用户提交用户名密码到登录接口
- 后端校验通过后生成一个 JWT Token 返回给前端
- 前端把 Token 存到 localStorage 或 sessionStorage,后续请求在拦截器里自动附加到 Header
- 后端通过拦截器或过滤器解析 Token,获取用户信息并校验权限
实际开发时,JWT 的密钥管理、Token 过期时间、注销机制都是值得展开的细节。课程设计答辩的时候,你如果能解释清楚“为什么用 Token 而不是 Session”,基本上认证这一块就能拿高分。核心逻辑是:Session 依赖服务端存储,水平扩展时要做 Session 共享;Token 是无状态的,服务端只需要验签即可,更适合现在的分布式部署。
4. 前端 Vue3 实现细节:从接口对接到底层逻辑
4.1 Vite 与项目初始化
这套源码的前端项目大概率是基于 Vite 构建的。Vite 相比 Webpack 最大的优势是开发环境启动极快——Webpack 项目启动可能等 1 到 2 分钟,Vite 的按需编译方案基本一两秒就能起来。这背后是浏览器原生 ES Module 加载和预构建依赖的底层优化。
初始化 Vue3 项目的方式很简单:
npm create vite@latest schedule-web -- --template vue如果前端项目需要vue-router和pinia,再安装这两个依赖。这里我给新读者一个提醒:Vue3 项目里最常用的状态管理库已经从 Vuex 转向 Pinia。Pinia 的 API 更简洁,与 Composition API 的配合也更自然,后续维护这套源码时如果涉及状态管理,直接上 Pinia 就好。
4.2 路由与页面权限控制
前端路由的配置要配合后端返回的角色信息。Vue Router 4 里核心的概念是导航守卫,全局前置守卫可以拦截路由跳转,判断用户是否已登录以及是否有访问权限。
伪代码大概是这样:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else if (token && to.meta.role && !userInfo.roles.includes(to.meta.role)) { next('/403') } else { next() } })4.3 Axios 拦截器与 API 封装
前端对接后端接口,最忌讳的就是在每个页面组件里直接写axios.get(...)。正确的姿势是统一封装一个request.js模块,在里面配置 baseURL、超时时间、请求拦截器、响应拦截器。
请求拦截器统一附加 Token:
service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })响应拦截器统一处理业务码和异常状态:
service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(error.message || '网络异常') return Promise.reject(error) } )4.4 课表页面的动态渲染实现
课表管理系统的前端最核心的页面是“课表展示页”。这个页面的实现思路本质上是“二维网格渲染”:行是星期一到星期五,列是第 1 节到第 8 节。每一个单元格的内容根据课程表数据动态填充。
用 Vue3 的响应式数据来管理课程列表,用 computed 计算属性根据当前选中的教师或班级筛选出对应课程,然后渲染时通过比较课程的时间和当前网格的坐标决定是否填充。
伪代码可以简单写成:
<template> <table> <tr v-for="section in sections" :key="section"> <td>{{ section }}</td> <td v-for="day in weekDays" :key="day"> <div v-for="course in filteredCourses" :key="course.id"> <template v-if="course.weekDay === day && course.section === section"> <div class="course-item">{{ course.courseName }}</div> </template> </div> </td> </tr> </table> </template>这种渲染方式要注意的是性能问题。如果课程数据量大,每个单元格都要遍历一次课程列表,可能会造成卡顿。优化方式是提前根据“星期几 + 节次”做一层 Map 索引或分组,渲染时直接查 Map 而不是遍历全量数组。
4.5 前端集成 Element Plus 的常见踩坑
这套前端大概率用了 Element Plus 组件库,毕竟 Vue3 生态里它是最主流的选择。这里我说几个实际开发中经常遇到的问题:
第一,组件按需自动导入 vs 全量导入。课程设计项目建议全量引入,最简单省事:
import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' app.use(ElementPlus)公司级项目再考虑按需引入,用unplugin-vue-components插件做自动按需导入。课程设计阶段全量引入完全够用,踩坑少。
第二,ElMessage 样式不生效。这个问题经常出现在按需导入配置不完整的场景里——ElMessage 是函数式调用,不会走组件解析流程,需要单独引入它的样式。全量导入则没这个问题。
第三,表格组件表格列的自定义渲染。Element Plus 的el-table支持通过#default插槽自定义单元格内容,课表模块里的操作列、状态标签基本都会用到这种方式。实际做的时候记得插槽名规则是#default="{ row }",不要写成 Vue2 时代的slot-scope。
5. 系统的运行与部署:从源码到“跑起来”
5.1 环境准备清单
在动手部署之前,先把目标环境对照检查一遍。这套源码的默认运行环境是:
| 组件 | 版本建议 | 备注 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot2 对 JDK8 兼容最稳 |
| Maven | 3.6+ | 依赖管理工具 |
| MySQL | 8.0 系列 | 注意编码和认证插件 |
| Node.js | 16+(推荐 18) | 跑前端构建 |
| 包管理器 | npm 或 pnpm | pnpm 更快但需要 Node 版本支持 |
这里的核心搭配原则是“版本宁旧勿新”。很多同学把 JDK 升到 17 甚至 21,然后用 SpringBoot2,结果遇到 Spring 框架内部 CGLIB 代理或反射方面的问题,排查起来非常痛苦。SpringBoot2 配 JDK8 是最稳的黄金组合,没有之一。
5.2 后端启动完整步骤
后端启动的流程不复杂,但有几个细节非常容易忽略。我按实际操作顺序列一遍:
第一步,导入项目。用 IDEA 打开源码目录,第一次加载 Maven 依赖会花上几分钟,注意右下角的进度条。如果下载依赖卡住,检查 Maven 的 settings.xml 是否配置了国内镜像源——阿里云镜像能大幅提升下载速度。
第二步,修改配置文件。打开application.yml(或application.properties),重点检查:
spring: datasource: url: jdbc:mysql://localhost:3306/schedule_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的数据库密码数据库连接串里有几个参数值得说明:
serverTimezone=Asia/Shanghai必加,不加会报时区错误useSSL=false建议加上,避免 SSL 握手警告characterEncoding=utf8保证中文不乱码
第三步,创建数据库并执行 SQL。在 MySQL 里执行源码自带的 SQL 文件,这一步如果没问题,数据表就建好了。
第四步,启动应用。直接运行主类里的main方法。启动成功后控制台会打印 Tomcat 端口号(默认一般是 8080)。如果端口被占用,在配置里改一下server.port。
第五步,简单验证。浏览器访问http://localhost:8080/api/courses(如果接口路径不需要鉴权),看一下能否返回 JSON 数据。或者先访问登录接口POST /api/login,试试初始账号密码能否正常登录。
5.3 前端启动完整步骤
前端项目的启动比后端更简单,依赖环境准备好后三步搞定:
cd schedule-web npm install npm run devnpm install阶段如果下载缓慢或卡死,可以切到国内镜像源:
npm config set registry https://registry.npmmirror.com开发服务器默认端口一般是 5173,浏览器打开http://localhost:5173就能看到前端页面。此时前端调用的后端接口地址需要在环境变量或 axios 配置里指向http://localhost:8080,注意跨域问题——开发环境下 Vite 的 proxy 配置可以优雅解决跨域,生产环境你则需要处理 Nginx 反向代理。
5.4 Docker 方式运行 MySQL8.0 的推荐姿势
如果你本地不想装 MySQL 或者装机环境太乱,用 Docker 跑 MySQL8.0 是一个省心的方案。我平时自己拉开发库基本都走 Docker,几分钟起一个实例,用完就删,干净利落。
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e TZ=Asia/Shanghai \ mysql:8.0注意这里有几个参数的含义:
-d:后台运行--name:容器名称,后续操作要用-p 3306:3306:宿主端口映射到容器端口-e MYSQL_ROOT_PASSWORD:指定 root 密码-e TZ=Asia/Shanghai:设置容器时区,避免 MySQL 时间差 8 小时
按上面方式启动后,需要手动进入容器创建数据库:
docker exec -it mysql8 bash mysql -u root -p在 MySQL 里执行建库语句。这里我特别强调一点:如果是首次用 Docker 跑 MySQL8.0,记得确认容器和宿主机的时区设置一致,否则课表里的时间显示会整体偏移 8 小时,排查起来很烦。
5.5 打包部署的关键命令
本地跑通之后,如果要部署到服务器或者演示环境,需要分别对前后端进行打包。
后端打成 jar 包:
mvn clean package -DskipTests java -jar target/schedule-backend.jar前端构建静态文件:
npm run build构建完成后,dist 目录里就是可部署的静态文件。把 dist 里面的内容放到 Nginx 的 html 目录下,配置反向代理把/api开头的请求转发到后端服务,整体的部署就算完成了。Nginx 里的核心配置片段类似这样:
server { listen 80; server_name your-domain.com; root /path/to/dist; index 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; } }这个配置解决了两个问题:一是前端路由的 history 模式刷新 404,二是前端静态资源与后端 API 的同域访问,避免额外处理跨域。
6. 常见问题与排查技巧实录
6.1 数据库连接失败的三种场景
场景一:Access denied for user。
最典型的错误提示是Access denied for user 'root'@'localhost'。排查思路很直接:账号密码是否正确?MySQL 是否允许当前主机的连接?如果密码里有特殊字符,注意 YAML 配置文件中是否需要转义。另外确认 MySQL 服务确实在运行——Windows 下按Win+R输入services.msc找到 MySQL 服务看看状态,Linux 下执行systemctl status mysql或service mysql status。
场景二:Public Key Retrieval is not allowed。
这个报错只在 MySQL8.0 下出现,原因是驱动默认不允许获取服务端公钥。解决方案是在 URL 连接串里加一个参数:
allowPublicKeyRetrieval=true同时可以把useSSL=false加上,这个组合能解决绝大多数 8.0 的连接认证问题。
场景三:Communications link failure。
这个报错情况比较复杂,可能是 MySQL 服务没起来、端口被占用、或者网络不通。排查优先级:先确认 MySQL 进程在,再确认端口能连通:
telnet localhost 3306Linux 下如果 MySQL 装在 Docker 里,还要确认端口映射是否正确暴露到宿主机。
6.2 前端接口 404 与跨域问题
通过 Vite 启动前端后,登录时报 404 或跨域报错,这是最常见的两类问题。
跨域问题的判断标准是看浏览器控制台,如果包含CORS或Cross-Origin字样,基本可以确定是跨域。开发环境最优雅的解决方式是通过 Vite 配置代理:
// vite.config.js export default { server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }配置完成后,前端代码里请求的 baseURL 直接写成/api即可,浏览器访问的是前端地址,代理转发到后端。注意配置完代理后要重启 dev server,不然不生效。
如果报 404,先看后端接口路径和前端请求路径是否完全一致。常见是前端多了一个/api前缀而后端没走统一前缀,或者后端@RequestMapping的类路径与前端请求路径对不上。逐级比对 controller 的类头部注解和方法注解。
6.3 MyBatis-Plus 的字段映射失败
这个问题的经典报错是Invalid bound statement (not found)或者查询结果里某些字段全是 null。
先说Invalid bound statement:大概率是 Mapper 接口没有扫描到,或者 XML 映射文件路径不对。检查启动类@MapperScan注解的包路径,以及application.yml里的mybatis-plus.mapper-locations配置,确保 XML 文件在 resources 目录下可以被正常加载。
再说字段 null:多半是实体类的属性名和数据库表字段名不一致。MyBatis-Plus 默认开启驼峰映射,course_name能自动映射到courseName。但如果表字段命名不规范(比如cname想映射成courseName),就需要加@TableField注解显式指定。这套源码的表结构如果设计得规范,一般不会遇到这个问题,但如果你是拿这套系统二次开发,新增表的时候就要注意了。
6.4 前端数据不更新的响应式陷阱
Vue3 里最常见的响应式坑是用普通变量去接收接口返回值,然后数据变了页面不刷新。正确做法是数据必须存放在ref或reactive包裹的响应式变量里,并且注意数组和对象的操作方式。
我写过很多次这个例子,再强调一遍:
// 错误写法 let courses = [] api.getCourses().then(res => { courses = res.data // 页面不会更新 }) // 正确写法 const courses = ref([]) api.getCourses().then(res => { courses.value = res.data // 页面更新 })6.5 中文乱码问题排查
中文乱码可能出现在三个环节:
数据库存储乱码,检查建表时的字符集是否为 utf8mb4。MySQL8.0 默认已经是 utf8mb4,但如果你从 5.7 迁移过来,旧表的字段还是 latin1,就需要手动转换。
后端接口返回乱码,检查 SpringBoot 的编码配置,在 application.yml 里设置:
server: servlet: encoding: charset: utf-8 force: trueforce: true很关键,表示强制使用 UTF-8 编码,否则可能出现响应头里没带 charset,浏览器用系统默认编码解析导致乱码。
前端页面显示乱码,检查 index.html 里有没有<meta charset="UTF-8">,以及 JS 文件本身的编码是不是 UTF-8。
6.6 端口占用问题
后端 8080 端口被占用的报错是Port 8080 was already in use,前端 5173 端口被占用则是 Vite 会提示并自动换端口。处理后端端口占用,Windows 下可以执行:
netstat -ano | findstr 8080 taskkill /PID 进程号 /F或者干脆改配置里的server.port,比如改成 8081,省事。
7. 源码扩展与二次开发建议
7.1 引入 Redis 做验证码与 Token 缓冲
这套系统的认证如果用 JWT,其实 Redis 不是必须的。但如果你想要更完整的“记住登录状态”“注销即失效”能力,可以引入 Redis 做 Token 黑名单或白名单管理。JWT 的天然短板是签发后无法主动吊销,把 Token 存到 Redis 并设置过期时间,注销时删除 Redis 里的记录,就能实现真正意义上的“退出登录立即失效”。
7.2 增加 Excel 批量导入课表
课程设计的加分项,我首推 Excel 导入导出。用阿里开源的 EasyExcel 或者 Apache POI,做一个“上传 Excel 批量排课”的功能,比手动一条条录入体验好太多。操作流程是:前端上传文件到后端 -> 后端解析 Excel -> 数据校验(课程名非空、时间不冲突)-> 批量写入课程表 -> 返回导入结果明细。
这个功能对于课表管理系统来说几乎是从“演示系统”到“可用系统”的分水岭。我在实际做课程设计指导时,经常建议同学们加这个模块,投入产出比很高。
7.3 接入代码生成器提升开发效率
如果你打算在这套系统上再加几张表、加几个模块,强烈建议直接用 MyBatis-Plus 的代码生成器。引入mybatis-plus-generator依赖后,配置好数据库连接和目标包路径,它能自动生成实体类、Mapper 接口、Service、Controller 和前端不需要的 XML,一套模板跑下来,一个新的 CRUD 模块的业务代码基本出来大半。
当然,代码生成器生成的东西只能保证“跑得通”,不保证“写得好”。业务流程中比较复杂的查询和事务逻辑,还是要手动去改 service 层。我见过不少同学直接拿生成代码当最终代码提交,评审的时候被问到模块间的逻辑直接答不上来,这个大家要看清楚边界。
7.4 前端可视化排课体验优化
目前课表页面大概率是一个静态表格。可以优化的方向是:拖拽排课——把课程卡片从左侧课程列表中拖到课表网格里,自动写入对应时间段。Vue3 生态里可以配合vuedraggable或者原生 HTML5 拖拽 API 实现。拖拽排课一旦做出来,整个系统的交互质感会提升一个量级,同时展示了你对前端事件系统和状态管理的掌握程度。
8. 实操心得总结
把整套系统完整跑通、读透、并自己动手改一改之后,我个人的体会有几点,分享出来供参考。
第一,这套技术栈组合的学习价值大于业务价值。课表管理系统本身业务不复杂,但选的技术栈面很广——从数据库设计到后端接口开发,从前端响应式渲染到前后端联调,几乎每个 Java Web 开发的核心知识点都覆盖到了。认真跟一遍代码,比看十篇零散的博客有用得多。
第二,源码能否二次扩展,重点看它留下的空间。我拿到这套源码之后,第一件事是看配置文件、看数据库表结构、看 controller 层的接口数量。这几个地方能反映出代码的规范程度和可维护空间。如果你发现application.yml里的数据库配置是硬编码的、接口没有统一返回体、表结构里没有创建时间和更新时间字段,那么二次开发的成本会比较高。
第三,调试过程中遇到问题时,养成按“配置 -> 代码 -> 数据”三个层面排查的习惯。八成的问题出在配置上——URL 写错、版本不匹配、依赖缺失;两成在代码逻辑;数据层面的问题相对少见。按这个优先级排,能省下大量瞎折腾的时间。
最后再分享一个小经验:任何一套源码拿到手,先不改业务,先把它的启动方式、目录结构、核心流程完整跑通,再谈修改和扩展。磨刀不误砍柴工,这个习惯能让你在一周内从一个“会跑项目的用户”变成一个“能改项目的开发者”。