ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpringBoot+Vue前后端分离选课排课系统设计与实现

SpringBoot+Vue前后端分离选课排课系统设计与实现 简介一套基于SpringBootVue的学生网上选课排课系统毕业设计项目面向Java Web学习者与毕业设计场景采用前后端分离和B/S模式包含管理员、教师、学生三类角色。后台管理覆盖学生管理、选课限制、选课信息、排课信息、公告信息、课程信息、教师管理、学生成绩、token管理等功能模块数据库脚本随包提供数据库连接可在application.yml中修改项目支持IDEA或Eclipse运行。压缩包整体约17.96MB内含SpringBoot后端源码、Vue前端源码、数据库脚本和毕业设计论文文档便于读者对照理解系统设计、接口结构与数据库关系。目前已有224人学习下载适合毕业设计参考、选课排课系统二次开发以及前后端分离项目实践练习。1. 为什么拿SpringBootVue做选课排课比单页教科书项目更有参考价值选课排课这个题目很多同学第一反应是“太普通了”但真到了并发高峰或者课表冲突调不动的时候才知道一张选课记录表背后要卡多少业务条件。我这个项目是基于SpringBoot和Vue做的一个前后端分离学生选课排课系统后端负责权限、选课限制、排课冲突检测和成绩维护前端负责管理员、教师、学生三类角色的操作界面整个工程按B/S模式组织适合作为毕业设计或课程设计的完整参考。它解决的问题很具体选课人数满了怎么办、同一教师时间冲突怎么办、token过期怎么处理——这些不是教科书里的边缘case而是你接手一个真实课务系统时最先遇到的坑。2. 前后端分工SpringBoot如何把B/S三层拆成可以同时调试的服务B/S模式对学生选课系统来说最直接的好处是客户端只需要浏览器不必为每个用户安装软件。这个项目把前端Vue和后端SpringBoot拆成两个独立工程前端跑在Vite开发服务上后端跑在Tomcat上通过HTTP接口通信。这样的结构让管理员、教师、学生三个角色的权限判断全部落到后端service层前端单纯做展示和数据收集这也是线上项目里最常见的拆法。2.1 前端Vue的定位与后端分层结构前端部分负责三件事路由跳转、表单校验、数据渲染。比如学生登录后进入选课页页面先向后端请求课程列表拿到JSON数据后渲染成表格点击“选课”按钮时再把studentId和courseId传给后端。真正的选课人数是否超限、是否重复选课这些判断都不放在前端因为前端校验可以被绕过后端校验才是安全边界。后端按SpringBoot的分层惯例拆成controller、service、mapper、entity四层。入口就是项目根目录下的Application.java直接run就能启动。这里有一个容易被忽略的点MapperScan注解必须指向mapper接口所在的包否则Mapper XML和接口对不上启动时不报错一调接口就抛“Invalid bound statement”异常。SpringBootApplication MapperScan(com.example.selectcourse.mapper) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }SpringBootApplication把自动配置、组件扫描和配置类绑定三个注解合并在一起MapperScan告诉MyBatis到哪里去找Mapper接口。如果你把这段代码粘到自己的工程里记得把com.example.selectcourse改成你项目实际的包名否则Spring扫描不到任何Bean。2.2 application.yml配置与Maven版本选择数据库连接都放在src\main\resources\application.yml里。这个文件是整个后端服务启动时最先读取的配置包括端口、数据库连接池、MyBatis映射位置。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/student_course?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.selectcourse.entityuseUnicodetruecharacterEncodingutf8是必须加上的不然数据表里中文会变成乱码serverTimezoneAsia/Shanghai解决MySQL 8.x时区偏差问题。如果把这一串URL写错项目能启动但第一次查询数据库时大概率报The server time zone value相关的错。项目里标注的Maven版本是apache-maven-3.3.9这个版本比较老但配JDK 1.8非常稳。实际开发时用Maven 3.6.3也没有问题关键是不要在JDK 8环境里强行用JDK 17配套的SpringBoot版本否则编译器会报依赖不兼容。类似“springboot版本太高导致启动失败”这类问题多半不是框架本身有bug而是pom里引入的第三方包和JDK版本打架。2.3 角色模块与功能映射系统里管理员、教师、学生三类角色对应后端不同的controller。管理员负责全局配置教师可以录入成绩和查看排课学生只做选课和查询。下表列了核心模块和对应职责模块角色后端对应Service学生管理管理员StudentService课程信息管理员CourseService选课限制管理员SelectionLimitService排课信息管理员/教师ScheduleService公告信息管理员NoticeService学生成绩教师ScoreServicetoken表所有角色TokenService这样的模块划分避免了“一个大Service里堆几百行方法”的问题。选课排课系统的业务逻辑并不复杂但权限边界一定要清晰。管理员能调用的接口如果学生也能调用那整个系统就没有安全可言了。3. 数据库落库从db.sql到ER图选课排课的表结构该怎么设计数据库设计是这类系统里最花时间的部分。项目提供了一份db.sql用Navicat或者dbx数据库工具执行一遍就能把库建出来。表结构的设计思路和网上流传的ER图基本一致学生选专业、课程关联教师、选课记录关联学生和课程成绩再单独挂到学生和课程之下。3.1 核心表结构与建表SQL选课系统的核心表一般有五张用户表、课程表、选课记录表、排课表、公告表。实际项目里还会拆出专业表、院系表但那份db.sql已经把它们合并成了字典表模块用字典值维护学院和专业名称。这样做的好处是减少关联查询坏处是统计院系人数时要用group by加join处理。CREATE DATABASE IF NOT EXISTS student_course DEFAULT CHARACTER SET utf8mb4; USE student_course; CREATE TABLE sys_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT STUDENT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( id INT NOT NULL AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, max_count INT DEFAULT 50, selected_count INT DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE selection ( id INT NOT NULL AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;sys_user表里的UNIQUE KEY uk_username可以防止重复用户名selection表里UNIQUE KEY uk_student_course是数据库层的选课防重就算后端漏判了重复选课数据库也会抛异常拦下来这是双保险。很多没有经验的项目只做应用层判断一旦并发请求同时进来两条记录都能插入成功到后期清理数据才发现问题。3.2 数据库连接与初始化数据拿到db.sql之后不要直接双击先确认MySQL版本。如果你的环境是MySQL 8.0以上脚本里的utf8最好改成utf8mb4不然存emoji字符会报错。执行完脚本再在application.yml修改数据库账号密码这一步是让后端连上本地库的关键。实际项目中我一般用下列思路检查连接是否成功启动后端服务后打开浏览器访问http://localhost:8080/api/actuator/health如果返回{status:UP}就说明服务正常。如果你的项目没有引入actuator就直接调用一个最简单的登录接口看数据库是否返回错误日志。3.3 成绩表与字典表的设计取舍成绩表不要和学生表混在一张表里。很多毕设项目大一统地建一个“用户表”包含成绩和选课信息虽然写起来简单但后续分权管理会非常痛苦。严格按照db.sql的划分成绩单独维护学生ID、课程ID、分数、录入教师ID。表名核心字段主要用途sys_userid, username, password, role登录认证courseid, course_name, teacher_id, max_count课程与人数上限selectionid, student_id, course_id选课记录scheduleid, course_id, teacher_id, weekday, period排课冲突检测scoreid, student_id, course_id, score, teacher_id成绩录入noticeid, title, content, create_time公告展示字典表模块在这个项目里起到配置中心的作用例如学院、专业、班级这些枚举值都抽到字典表里。好处是后续增加新专业时不用改代码直接在管理后台插一条记录即可。开发时要注意字典表每次请求都会读取建议加一层缓存否则接口响应时间会随着字典数据增多而变慢。4. 选课限制、排课冲突与token表后端核心逻辑从哪下刀这一章是全文最关键的部分。网上模板很多但真正决定系统能不能跑起来的是三件事登录态怎么维持选课名额怎么扣减排课冲突怎么检测。这个项目用token表模块来保存登录态而不是依赖Spring Security的传统session这样前后端分离环境下前端任何请求都可以携带token进行验证。4.1 token表模块与登录验证流程管理员、教师、学生登录时后端校验用户名和密码成功后生成一个随机token把userId和token写进token表然后把token返回给前端。前端存储到localStorage后续请求带上这个token即可。这种实现方式用一张表解决了Spring Security的全套会话管理对于数据量不大的课务系统来说简单直接。Service public class AuthService { private final UserMapper userMapper; private final TokenMapper tokenMapper; public String login(String username, String password) { SysUser user userMapper.findByUsername(username); if (user null || !password.equals(user.getPassword())) { throw new RuntimeException(用户名或密码错误); } String token UUID.randomUUID().toString().replace(-, ); tokenMapper.insert(user.getId(), token); return token; } public boolean validate(String token) { Token t tokenMapper.findByToken(token); return t ! null t.getStatus() 1; } }这里的密码校验是明文对比实际生产环境一定要用BCrypt或者至少加盐哈希。放在毕业设计里明文对比能跑通整个流程但面试官问到安全问题时如果答不上来就尴尬了。比较好的折中方案是在注册接口里先做BCrypt.hashpw(password)登录时再做BCrypt.checkpw改动量小答案也能讲清楚。4.2 选课限制模块人数上限与去重选课限制模块是这个系统的业务核心。一个学生最多选几门课、每门课最多选多少人、是否限制重复选课都在这块逻辑里控制。管理员在后台设置这些参数学生前端不可见。Override public boolean chooseCourse(Integer studentId, Integer courseId) { Course course courseMapper.selectById(courseId); if (course null) { return false; } if (course.getSelectedCount() course.getMaxCount()) { return false; // 已满了 } Selection selection selectionMapper.findByStudentIdAndCourseId(studentId, courseId); if (selection ! null) { return false; // 重复选课 } selectionMapper.insert(studentId, courseId); courseMapper.increaseSelectedCount(courseId); return true; }这里使用了一个简单的乐观判断先查询再更新。并发不大的情况下够用。如果选课系统需要支撑高并发场景正确做法是引入Redis加分布式锁或者在数据库层面用UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count max_count来做原子限制。用后一种方式时选择受影响行数来判断是否选课成功就不需要先查出selected_count。4.3 排课信息模块冲突检测排课模块解决的核心问题是一个老师不能在同一时间出现在两个教室一个教室也不能在同一时间被两门课占用。db.sql里的排课表保存了weekday和period两个字段分别表示星期几和第几节通过这两个字段组合完成最基础的冲突检测。public boolean checkTeacherConflict(Integer teacherId, Integer weekday, Integer period) { ListSchedule schedules scheduleMapper.findByTeacherId(teacherId); return schedules.stream().anyMatch(s - s.getWeekday().equals(weekday) s.getPeriod().equals(period)); }这个逻辑可以继续扩展。比如把教室ID也加入约束变成teacherId weekday period和classroomId weekday period两组唯一性校验。如果排课表里已经有UNIQUE KEY uk_teacher_time的数据库约束后端漏判时数据库会再次拦截和选课表一样是双保险。4.4 成绩管理与公告信息模块成绩模块由教师录入逻辑不复杂。要注意的是教师在录入时只能看到自己教的课程不能在应用层把课程选择和教师绑定漏掉。公告信息模块则是在管理员发布后按时间倒序展示前端分为置顶和普通两类如果要做得更细可以增加发布状态字段未发布的公告只对管理员可见发布后才对学生可见。整体上看这些模块都是围绕课程主键展开的CRUD真正体现系统复杂度的地方仍然是选课限制和排课冲突这两块。5. Vue前端联调、安全配置与上线前的最后验证到达这一步时前后端接口已经可以调通。剩下来的工作就是把前端的开发环境代理配好确保token能带上再按线上规范做一次完整走查。这一章说的都是实际联调中容易出现问题的点。5.1 开发环境代理与axios封装前端调用后端接口时最常遇到的两个问题是跨域和token丢失。先用Vite或Webpack的代理把/api前缀转发到后端服务module.exports { devServer: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };接着在axios拦截器里统一添加Authorization头避免在每个接口里重复写token。import axios from axios; const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); export default service;changeOrigin: true确保后端收到的请求来源是localhost:8080防止部分CORS配置下的Origin校验失败。axios一旦统一封装后续写接口时只需要调用service.get(/course/list)token会自动携带。5.2 环境配置与部署验证部署前把VUE_APP_BASE_URL这类环境变量配到.env.production文件里图片资源和接口地址不要写死。启动后端服务后先验证登录接口是否正常返回token再带着token请求课程列表接口两个接口都通了系统才算真正跑起来。curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123} curl -X GET http://localhost:8080/api/course/list?page1size10 \ -H Authorization: Bearer $TOKEN如果登录接口返回了HTML而不是JSON说明请求路径没有打到对应controller如果返回500查看后端日志中的堆栈信息。用dbx数据库工具连接学生选课库手工插入一条学生选课记录再在前端页面点击取消选课观察数据库记录是否删除这个闭环验证通过后系统的主要业务链路就稳定了。“springboot mybatis 当表不存在自动建表”这类功能在这个项目里没有启用我做毕设时更倾向显式执行db.sql这样表结构可控出问题时也能快速定位。本文还有配套的精品资源点击获取
返回列表