尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
前后端分离课表管理系统实战:SpringBoot+Vue+MyBatis全栈开发
我最早接到这个课表管理系统的需求时项目方只给了一句话——把学校的排课、调课、查课搬到线上。当时的技术方案其实已经比较固定了前后端分离架构后端用SpringBootMyBatisMySQL前端用Vue这也是目前JavaWeb毕业设计和中小型管理系统中非常成熟的一套组合。前后端分离、SpringBoot、Vue、MyBatis、MySQL这套技术栈既能满足课表管理系统这种典型CRUD业务又能在答辩或面试时讲清楚每一个模块的分工性价比很高。这篇内容不只是讲项目怎么搭我会把整个项目的设计思路、核心表结构、接口约定、前端页面实现、联调排错、部署上线的完整链路都拆开讲一遍。无论你是拿来做毕业设计还是想练手一个完整的前后端分离项目只要能跟着把思路捋一遍课表管理系统这个题目基本不会再有什么盲区。1. 项目整体设计与技术选型为什么这么搭1.1 课表管理系统的核心需求拆解先把业务看清楚再谈技术这是我一贯的做法。课表管理系统表面上是增删改查但实际拆开之后至少包含三个角色、四类核心数据、六个核心场景。三个角色分别是管理员负责排课、调课、发布学期课表、教师查看个人课表、申请调课、学生按班级或学号查看课表。四类核心数据是班级、教师、课程、教室加上一个维护周次节次的时间维度。六个核心场景分别是管理员排课、课表冲突检测、班级课表查询、教师课表查询、教室占用查询、调课申请与审批。这些需求决定了系统的功能边界。你在做需求分析时最容易犯的错误是一上来就写代码。我建议先用半小时把角色和用例图列出来哪怕只是画在白纸上后面建表、设计接口都会顺畅很多。课表管理系统尤其不要在排课算法上过度设计大多数实际项目只要做冲突检测人工调整就够用了贪心排课算法可以放到论文或文档里作为进阶讨论但不应该成为核心交付物。1.2 为什么选SpringBootVueMyBatisMySQL这套组合选型这件事我不太推荐追新。SpringBoot在目前Java后端项目里几乎成了默认起点它内置Tomcat、自动配置、生态完善适合快速搭建RESTful接口。MyBatis作为数据访问层相比Spring Data JPA来说SQL更可控课表管理涉及多表联查、周次判断、教室时段冲突判断这类复杂SQLMyBatis的XML写法能把SQL逻辑完整暴露出来方便优化和排查。MySQL则是中小型系统最稳妥的选择部署简单社区资料多出问题随便一搜就有答案。前端选择Vue主要看中两点一是组件化和响应式数据非常适合课表这种数据结构固定但交互状态多的页面二是Vue脚手架和生态成熟Element UI等组件库能直接提供表格、弹窗、表单校验省去大量UI重复劳动。这套组合本质上是在开发效率和可控性之间找平衡。不过选型时有一个容易忽略的坑版本兼容。SpringBoot 2.x和3.x差别很大3.x要求JDK17很多学校机房或云服务器还在用JDK8。网上不少教程是SpringBoot 2.x写的你如果直接上手最新版3.xMyBatis、PageHelper这些老牌依赖经常出现不兼容。稳妥做法是SpringBoot用2.7.x对应JDK8或JDK11Vue用2.6.x配Element UI或Vue3配Element Plus。这篇内容里的配置方案按SpringBoot 2.7.x MyBatis MySQL 5.7/8.0 Vue2来写是当前兼容性最稳的组合。2. 后端核心模块设计与实现要点2.1 数据库表设计与MyBatis映射关系课表管理系统的核心表我建议这样设计用户表、教师表、学生表、班级表、课程表、教室表、教学任务表也就是排课结果表。下面给出我实际项目里用过的精简版表结构字段去掉冗余只保留能跑通完整业务的部分。表名核心字段说明sys_userid, username, password, role, teacher_id登录账号角色区分管理员/教师/学生teacherid, name, title, department教师基础信息studentid, name, class_id, major学生信息关联班级classid, name, grade, major班级信息courseid, name, credit, hours, course_type课程信息classroomid, name, building, capacity, type教室信息scheduleid, class_id, course_id, teacher_id, classroom_id, week_start, week_end, day_of_week, section_start, section_end, semester排课结果一条记录代表某班某课程在哪些周、星期几、哪几节上课这里重点说schedule表。课表的时间维度由三个字段组合表达week_start/week_end表示起始周和结束周day_of_week表示星期几section_start/section_end表示第几节到第几节。这种设计比周次用逗号分隔字符串更规范可以直接用SQL做区间判断比如判断某教师是否在指定时间冲突SELECT COUNT(*) FROM schedule WHERE semester #{semester} AND teacher_id #{teacherId} AND day_of_week #{dayOfWeek} AND (#{sectionStart} section_end AND #{sectionEnd} section_start) AND ((#{weekNum} BETWEEN week_start AND week_end) OR (week_start BETWEEN #{weekNum} AND #{weekEnd}))这条SQL是冲突检测的核心逻辑是区间重叠判断新排课程的节次区间和已有记录的节次区间相交并且周次也相交就判定冲突。很多初学者会把冲突检测写在Java代码里循环判断数据量小没什么问题但数据量上来之后写SQL比Java循环高效得多也更清晰。MyBatis映射方面我习惯用XML而不是注解。像schedule表联查course、teacher、class、classroom这种场景注解拼接SQL可读性很差XML里可以把表关系、WHERE条件、resultMap都写得清清楚楚。一个常用技巧是写一个查询课表详情的resultMap把四张关联表的字段映射好前端拿到的就是一个完整的课表条目对象不需要再二次请求。2.2 SpringBoot业务层权限校验与业务边界课表管理系统的后端controller层我做得比较薄只负责接收参数、调用service、返回统一结果。业务逻辑放在service层这样接口复用性好比如管理员排课和课程导入共用同一个service方法。权限这块我推荐用SpringBoot内置的拦截器或者Spring Security做简单版本。如果不想引入太重的东西一个HandlerInterceptor就能解决80%的权限问题。我在项目里是这么做的用户登录成功后生成一个Token用拦截器拦截需要鉴权的路径从Token解析出用户角色再用注解或手工判断当前接口允许哪些角色访问。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 解析token判断用户是否登录、角色是否匹配 if (token null || !TokenUtils.verify(token)) { response.setStatus(401); return false; } return true; } }当然如果你项目时间宽裕或者论文需要体现安全性设计直接上Spring Security也是合理的。但要注意一点Spring Security的配置复杂度和版本坑很多如果只是做一个课表管理系统Interceptor方案在答辩时反而更好讲清楚因为它直观。业务层的另一个重点是事务。排课时要同时插入schedule记录、更新教室占用状态、记录操作日志必须加上Transactional避免中途失败导致数据不一致。我踩过这样一个坑排课时教室冲突检测通过但插入时教室表更新失败结果schedule表里多了记录教室状态没变导致后续排课数据错乱。加了事务之后这类问题就从根源上消失了。2.3 接口设计规范统一返回结构与前端约定前后端分离项目接口规范是协作的地基。如果每个接口返回结构都不一样前端写起来会疯掉。我的统一返回结构是{ code: 200, message: 操作成功, data: { } }code的取值范围自己约定200表示成功401表示未登录403表示无权限500表示服务器异常。前端Axios拦截器统一判断code非200就直接弹错误提示省去每个页面单独处理错误逻辑的重复劳动。课表管理系统的核心接口大致如下POST /api/login 登录GET /api/schedule/teacher/{teacherId} 教师个人课表GET /api/schedule/student/{studentId} 学生班级课表GET /api/schedule/classroom/{classroomId} 教室占用情况POST /api/schedule 新增排课管理员PUT /api/schedule 调课管理员DELETE /api/schedule/{id} 删除排课管理员GET /api/schedule/conflict/check 冲突检测接口命名要统一用小写复数名词路径参数用花括号查询条件用query参数。前端和后端接口联调的时候最好维护一份接口文档哪怕是简单的Markdown表格也行。我见过太多项目前后端各干各的最后联调时对不上参数名白白浪费两三天时间。3. 前端Vue项目搭建与页面实现3.1 Vue环境配置与项目初始化完整过程前端环境配置是新手特别容易卡住的地方这里把完整流程写一遍。首先安装Node.js建议装14.x或16.x这两个版本对Vue2和大部分依赖的兼容性很稳定。安装完成后命令行验证node -v npm -vnpm是Node自带的包管理器。但因为国内网络原因直接用npm下载依赖经常很慢甚至失败我建议先换镜像源npm config set registry https://registry.npmmirror.com换完之后再用npm config get registry验证一下显示的是国内镜像地址就说明生效了。接下来用Vue CLI创建项目npm install -g vue/cli vue create course-system-web脚手架会问你选择默认配置还是手动配置建议选手动Manually select features勾选Router和Vuex。项目创建完成后安装UI组件库npm install element-ui --save npm install axios --save装依赖的过程可能会报各种warning只要不出现npm ERR一般不用管。真的遇到版本冲突优先方案是删掉node_modules目录和package-lock.json重新执行npm install。3.2 路由、Vuex状态管理与Axios请求封装前端代码的骨架是路由。课表管理系统的页面不多我划分成这样几个模块登录页、管理员首页排课管理、教室管理、课程管理、教师管理、教师端课表页、学生端课表页。路由用Vue Router的懒加载写法const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /admin/schedule, component: () import(/views/admin/ScheduleManage.vue), meta: { role: admin } }, { path: /teacher/schedule, component: () import(/views/teacher/TeacherSchedule.vue), meta: { role: teacher } } ];路由守卫里判断登录状态和角色未登录跳转登录页角色不匹配拦截并提示无权限。这一步配合后端拦截器实现了双层权限控制。Vuex用来保存登录用户的token和角色信息刷新页面后从localStorage恢复。这个细节很关键如果你的token只存在Vuex里刷新页面就丢了用户必须重新登录体验很差。Axios请求封装是我比较重视的部分。我会创建一个request.js设置baseURL、请求拦截器自动携带token、响应拦截器统一处理codeimport axios from axios; import { Message } from element-ui; import router from ./router; const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res.data; } Message.error(res.message || 请求失败); return Promise.reject(res); }, error { if (error.response error.response.status 401) { router.push(/login); } Message.error(网络异常请稍后重试); return Promise.reject(error); } );这里有个细节baseURL我配置成/api而不是完整的后端地址。开发环境下由Vue CLI的devServer代理转发到后端端口生产环境由Nginx统一转发。这样前端代码里不会写死任何IP和端口换环境部署只需要改代理配置不用改代码。3.3 课表展示与排课交互的核心实现课表展示是前端最核心的组件。我用的方案是Element UI的el-table改造成的周课表行是节次第1节到第12节列是星期一到星期日每个单元格里渲染课名、教师、教室、周次范围。数据从后端接口拿到的是schedule列表前端按day_of_week和section_start进行分组渲染成二维数组。这里有几个体验细节值得注意。同一门课如果跨两个节次比如第3-4节单元格应该合并或者显示在起始行并设置rowspan否则表格看起来会错乱。我处理的方式是在表格数据构造阶段将同一星期连续节次的课程合并成一条信息放在起始节次的单元格里显示第3-4节 大学英语 张老师 教1-101。排课页面我做成一个可拖拽或点击选择的交互管理员选择学期、年级、班级后左侧显示该班已有课表右侧是可选的课程、教师、教室列表点击课程后弹出时间选择器选择完成后前端先调用冲突检测接口后端返回无冲突才允许提交。这个交互比传统的表单填写直观得多实测用了这个方案之后排课录入效率明显提升也避免了大量无效提交。4. 前后端联调与常见问题排查实录4.1 跨域问题的几种处理方式前后端分离项目联调时跨域是绕不开的第一个坎。前端跑在8080端口后端跑在8081端口浏览器会拦截跨域请求。我常用的解决方式有三种按推荐程度排序。第一种是后端配置CORS全局跨域。SpringBoot里加一个配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这种方式联调阶段最简单但上线时要收紧origin白名单不能长期使用通配符。第二种是前端开发环境用devServer代理这也是我推荐的方式。在vue.config.js里配置module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } };前端请求/api/scheduledevServer自动转发到后端的/api/schedule浏览器看到的只是同源请求不会有跨域报错。第三种是上线后由Nginx反向代理统一处理这个放到部署章节细说。4.2 日期时间格式与JSON序列化的坑前后端联调最容易翻车的还有日期格式。Java后端LocalDateTime默认序列化出来是一串类似2025-01-15T10:30:00的字符串前端要展示成2025-01-15 10:30:00这里经常出现格式不匹配。我通常在后端统一配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果用了LocalDateTime光配这个还不够需要在pom里引入jackson-datatype-jsr310模块并对LocalDateTime做序列化转换器配置。这属于典型的配置三分钟排查两小时问题提前配好能省很多事。课表系统还有一个时间相关的坑周次是数字类型但前端传过来可能是字符串。比如排课时选择第1-8周后端如果定义成Integer就会报类型转换错误。我统一约定前端传参全部按后端字段类型匹配后端接收参数用RequestBody接JSON对象不要散装接收多个参数这样能减少大部分类型不匹配问题。4.3 高频报错速查与排障思路报错现象可能原因解决思路前端请求一直404后端接口路径拼错或前端代理未生效先看后端控制台是否收到请求再用Postman直接调后端接口验证请求返回401token缺失或已过期检查前端请求拦截器是否携带token后端token解析是否成功数据库中文乱码连接串没加编码参数jdbc url加useUnicodetruecharacterEncodingutf8MyBatis查询结果为空实体字段和表字段映射不上开启mybatis驼峰映射或明确写resultMapSpringBoot启动报端口占用8081被占用换端口或杀掉占用进程Nginx部署后前端资源404前端路由是history模式刷新时Nginx找不到对应路径配置try_files回退到index.html再单独说一个MyBatis的经典问题一级缓存和二级缓存。默认情况下MyBatis开启一级缓存同一个SqlSession内有效二级缓存默认不开启。课表系统里如果多次查询同一课表其实没必要手动开启二级缓存因为数据更新频繁缓存失效问题反而更麻烦。我个人的建议是这个项目别在MyBatis缓存上花太多精力把SQL性能和索引做好更实际。5. 部署上线完整流程从源码到可访问5.1 后端打包与前端构建部署前先要把代码打包。后端在项目根目录执行mvn clean package -DskipTests打包产物在target目录下是一个可执行的jar包。打开这个jar里的application.yml确认数据库连接参数是服务器上的配置端口按照生产环境调整。SpringBoot内置Tomcat不需要额外安装直接java -jar就能跑。前端构建命令npm run build构建产物在dist目录里面是静态文件。到这里要特别注意dist里引用资源的路径取决于Vue项目的publicPath配置。如果你的页面部署在服务器根路径下publicPath用默认的/如果放在子路径要改成对应路径否则资源加载404。这个是我见过的高频坑部署自查时优先检查。5.2 服务器环境准备JDK、MySQL、Nginx服务器我以CentOS为例。先把基础环境装好yum install -y java-1.8.0-openjdk yum install -y mysql-server yum install -y nginx systemctl start mysqld systemctl start nginxMySQL安装完创建数据库和用户然后导入项目里的sql文件mysql -uroot -p create database course_system default charset utf8mb4; use course_system; source /root/course_system.sql;数据库导入的时候容易踩编码的坑。如果是Windows本机导出的sql文件拿到Linux上导入中文乱码多半是文件本身编码不是UTF-8。解决办法是导出时就明确指定UTF-8或者导入前先用file命令确认sql文件的编码。后端jar包和前端dist都上传到服务器后后端直接运行nohup java -jar course-system.jar --spring.profiles.activeprod course.log 21 用nohup让进程在后台运行日志输出到course.log。验证后端接口是否正常用curl测一下curl http://localhost:8081/api/health有响应说明后端起来了。5.3 Nginx反向代理配置与域名访问Nginx配置是整个部署的关键一环。我的做法是前端静态资源直接由Nginx托管后端接口通过反向代理转发。一个完整的server配置如下server { listen 80; server_name yourdomain.com; root /var/www/course-system/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8081/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }/api/路径转发到后端jar其余路径由前端路由接管。try_files $uri $uri/ /index.html这句尤其重要否则Vue Router的history模式下登录后按F5刷新页面就会404。配置完后执行nginx -t检查语法然后nginx -s reload重载。到这里整个系统就能通过浏览器访问了。记住一个排查原则前端报错先看浏览器Network面板后端报错看course.log数据库报错看MySQL日志。定位问题按这个顺序来基本不会抓瞎。6. 个人实操体会与几点建议课表管理系统项目的核心价值不在功能多新奇而在于把一套完整的前后端分离开发流程走通。我在做这个项目的过程中最深的体会有三点。第一数据库设计别贪多。schedule表我一开始设计的时候加了多余的课程类型学分字段后来发现完全用不上反而因为冗余字段让查询变慢。字段够用就行等真有需求再加。第二前后端接口约定必须文档化。哪怕只有一张Excel表把接口路径、请求参数、返回结构写清楚联调效率能提升一倍。我吃过一次亏排课接口最初叫/api/course/arrange后来统一改成/api/schedule前端所有调用处都要跟着改浪费了不少时间。第三版本搭配要克制。SpringBoot不要追最新版Vue也不要盲目升级到Composition API风格除非你对生态兼容有充分把握。稳定运行比花哨版本重要得多尤其是毕业设计或演示类项目确保开箱即跑才是第一目标。最后分享一个小技巧如果这个系统后续还要扩展可以考虑加一个课表导入导出Excel功能用Apache POI或EasyExcel实现管理员可以从教务系统导出的Excel批量导入课程数据能省掉大量手工录入时间。另外课表周次展示可以做成单周/双周的Color标志这个视觉效果很好排课数据里只要加一个week_type字段就能实现。希望这套完整的项目拆解能帮你少走点弯路。
RELATED

相关推荐

RabbitMQ实战全解:Java后端必备的消息队列异步解耦与可靠性指南

RabbitMQ实战全解:Java后端必备的消息队列异步解耦与可靠性指南

搞Java后端的人,迟早会碰消息队列。而RabbitMQ,几乎可以说是你入行后第一个绕不开的MQ。我最早接触RabbitMQ是在一个订单系统的重构项目里,那时候线程池加数据库轮询已经快撑不住了,每天凌晨的峰值流量能把库连接池打满。后来把Ra…

📅 2026/10/9 8:17:38
Ubuntu 24.04 conda环境Python服务开机自启:Ollama依赖管理实践

Ubuntu 24.04 conda环境Python服务开机自启:Ollama依赖管理实践

搞了一台Ubuntu 24.04的机器做本地大模型应用,推理服务用Ollama跑,业务逻辑是放在conda虚拟环境里的Python程序。需求很简单:机器重启后,两个东西都要自动起来,而且Python程序必须在Ollama之后启动。听起来挺常规&…

📅 2026/10/9 8:17:38
ModuleNotFoundError: No module named ‘protobuf‘ 的成因排查与版本修复实践

ModuleNotFoundError: No module named ‘protobuf‘ 的成因排查与版本修复实践

要说起 pip install 时报ModuleNotFoundError: No module named protobuf,我猜你多半是在装某个带客户端库的包、跑旧项目、或者刚拉下来一个开源仓库,执行安装后顺手一运行,直接在 import 阶段崩了。这个报错在 Python 环境里出现频率非常高…

📅 2026/10/9 8:17:38
MORE NEWS

更多资讯

📰

锐捷云桌面部署实战:从选型到运维的踩坑经验分享

1. 从一台老旧PC的报废说起:为什么我开始折腾云桌面办公室角落里那台五年前的品牌机,开机要三分半,打开浏览器再卡两分钟,硬盘灯狂闪像在求救。IT运维的同事每次路过都要叹口气,说这台机器再撑半年就该进回收站了。但问…

📰

SpringBoot考研帮平台:从表结构设计到Redis热榜与部署实战

每年九十月份,考研的QQ群和贴吧都会被同一类问题刷屏:“XX学校XX专业好不好考?”“有没有上岸学长学姐卖资料?”“数学一跟谁比较好?”——信息极度分散,真假难辨,今天问完明天就沉底。我当时做…

📰

T3 Stack 全栈开发实战:从脚手架到部署的完整链路与踩坑指南

最近一个月我把一个代号叫 t3code 的项目从零到一完整跑了一遍,这是一个基于 T3 Stack 做的在线代码片段管理应用,支持用户登录、代码片段增删改查、按语言/标签筛选,整体形态就是一个典型的全栈 CRUD 加认证的中小型应用。写这篇文章的起因是…

📰

Python变量命名规则与PEP 8规范:从标识符到实战避坑指南

很多新手会觉得,变量名就是个代号,只要不报错,怎么起都行。我刚学 Python 那阵也这么干,a 1、b 2、tmp满天飞。后来接手一个写了半年的项目,满屏data1、data2、temp_list,改一个功能要在三个文件里来回翻…

📰

C++ ADODB 数据库访问实战:从COM初始化到封装避坑指南

简介:针对C环境下的数据库交互需求,这份资源提供了一套基于ADO组件访问数据库的工程源码,适用于SQL Server、Oracle、MySQL等常见关系型数据库,适合有基本C语法基础、希望掌握COM数据库编程的开发者。压缩包内含ADODatabase.h和AD…

📰

从零搭建算法刷题题单目录:知识域划分与复盘方法

1. 刷题这件事,为什么需要一份"题单目录"先说个扎心的现实:很多人在算法面试或者日常训练中,刷了三四百道题,一到真正需要输出的时候,脑子里还是一团浆糊。遇到新题就像碰到陌生人,感觉似曾相识&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬