简介:一份面向计算机类毕业设计/课程设计的学生选课系统完整源码包,基于SpringBoot+Vue+MySQL实现前后端分离。系统覆盖管理员、教师、学生三类角色:管理员可维护专业、教师、学生和课程信息,查看选课情况与成绩;教师能查询学生与课程、审核学生选课并为课程打分;学生则支持选课、查询已选课程及成绩,功能贴合高校教学管理场景,适合需要可运行项目参考的高校学生和Java后端学习者。包内共126个文件,以97个Java源码为核心,搭配Vue前端JS/CSS、SpringBoot配置(YML/XML)、MyBatis映射及SQL初始化脚本,同时提供一键部署bat脚本、项目说明文档、开题报告和PPT答辩材料,压缩包仅3.83MB,目录按后端、前端、数据库、文档分层,结构清晰,按模块阅读和二次开发都很方便。已有61人学习下载。借助完整前后端代码、数据库脚本和部署文档,可快速在JDK1.8+MySQL5.7环境下启动项目,深入理解选课业务逻辑、数据库设计及前后端接口交互,也能为毕业设计文档撰写和答辩汇报提供实例参考。
1. 一套能撑起毕业答辩的选课系统,源码里藏着三条启动脚本
每年这个时候都有人拿着 java 毕业设计、springboot、vue、mysql 的选课系统源码来找我问"怎么跑起来"。这套学生选课系统就是典型的完整交付:后端 SpringBoot + MyBatis,前端 Vue.js,数据库 MySQL 5.7,还配了开题报告、说明文档和初始化 SQL。管理员管专业、教师、课程和学生;教师审核学生选课、给学生打成绩;学生参与选课、查选课信息、查已选课程成绩。它能解决的是从零搭建到答辩演示的全流程问题,适合正在做课程设计、毕业设计,以及第一次接触前后端分离项目的 Java 学习者。更难能可贵的是,压缩包里直接给了 1-install.bat、2-run.bat、3-build.bat 三条启动脚本,不用自己猜构建顺序。
2. 环境对齐:为什么这套选课系统必须踩在 JDK 1.8 和 MySQL 5.7 上
2.1 版本红线与选型理由
拿到源码先别急着双击脚本,第一步是对环境。这套项目的技术栈决定了很多坑藏在版本里:SpringBoot 用的是 Java 8 时代的版本,MySQL 用的是 5.7,Maven 用的是 3.3。你要是机器上装了 JDK 17 或者 MySQL 8.0,后面每一步都会出幺蛾子。
我一般会先按下面这张表把本机环境对齐,再往下走:
| 软件 | 本项目要求的版本 | 为什么不能随便升级 |
|---|---|---|
| JDK | 1.8 | SpringBoot 早期版本基于 Java 8 编译,JDK 11/17 下常见 javax 包缺失、Lombok 失效 |
| MySQL | 5.7 | db.sql 里的建表语句与 5.7 的 sql_mode 匹配,MySQL 8 的驱动类名和认证方式都变了 |
| Maven | 3.3 | 项目里有 mvnw.cmd,wrapper 会优先使用项目绑定的 Maven 版本,减少本机差异 |
| Navicat | 11 | 连接 MySQL 5.7 不需要处理 8.0 的 caching_sha2_password 认证兼容问题 |
先用一组命令确认本机状态,这一步不用装任何东西:
java -version # 输出里要有 1.8.0_xx,而不是 11.0.x 或 17.0.x mvn -v # 确认 Maven 版本,有 mvnw.cmd 时可以跳过 mysql --version # 确认 MySQL 是 5.7.x,8.x 后面会踩驱动坑三条命令分别对应 Java 编译环境、Maven 构建环境、数据库运行环境。java -version看的是 JDK 主版本,SpringBoot 2.x 在 JDK 8 下最稳;mysql --version看的是数据库大版本,它决定了你后面用哪个驱动类名。
如果本机版本不对,我的建议是直接装 JDK 1.8 和 MySQL 5.7,不要试图升级项目去适配新环境。毕业设计项目的时间成本应该花在理解代码上,不是花在环境迁移上。
2.2 从压缩包目录反推项目结构
解压后你大概会看到.classpath、mvnw.cmd、app.541519f5.css、chunk-vendors.0fdd4e14.css、学生选课系统.doc、开题报告.docx这些文件混在一起。很多人看到这堆文件就懵了,其实从文件列表就能反推项目结构。
.classpath是 Eclipse 的工程描述文件,说明这个项目最早是用 Eclipse 建的,但你用 IDEA 导入时不需要管它,直接定位到pom.xml以 Maven 项目导入就行。mvnw.cmd是 Maven Wrapper 的 Windows 执行脚本,它存在的意义是锁定 Maven 版本,不让本机全局 Maven 干扰构建。
两个 CSS 文件名里带了哈希值(541519f5、0fdd4e14),这是 Vue CLI 打包后的产物命名规则,说明前端代码已经构建过。也就是说这个压缩包不是只有源码,是连构建产物一起给你的。学生选课系统.doc和开题报告.docx是写论文要用的,和代码运行没关系,但答辩前一定要翻一遍,里面写了设计思路和功能说明。
2.3 三条批处理脚本的真实执行顺序
压缩包里的 1-install.bat、2-run.bat、3-build.bat,从命名就能看出执行顺序:先 install,再 run,最后 build。这是有讲究的,不能乱。
按文件名和 Maven 生命周期推断,1-install.bat做的事是依赖安装和本地仓库构建,可能包含数据库初始化提示;2-run.bat是启动后端服务,让 SpringBoot 跑起来;3-build.bat是构建前端产物并合并到 SpringBoot 的静态资源目录。如果你跳过 install 直接 run,大概率会碰到依赖缺失导致的ClassNotFoundException或NoClassDefFoundError。
我在 IDEA 里一般不直接双击 bat,而是手动拆成命令行执行:
mvn clean install -DskipTests # 先编译并安装依赖到本地仓库,跳过测试加快速度 mvn spring-boot:run # 启动后端,默认端口 8080前端部分在另一个终端里做:
cd frontend # 前端目录名以解压结构为准,有些包叫 web npm install # 安装 node_modules 依赖 npm run build # 构建产物到 dist 目录这里mvn clean install的clean会删掉 target 目录重新编译,避免旧 class 文件残留;-DskipTests跳过单元测试,毕业设计项目一般没有完整测试用例,跳过后构建速度更快。前端npm install装的是 Vue 依赖,npm run build打包后会生成带哈希的 CSS 和 JS 文件,就像压缩包里那两个 CSS 一样。
提示:bat 脚本双击报错最常见的原因是项目路径里有中文,cmd 对中文目录名处理经常翻车,先把项目放到纯英文目录下再执行。
3. 数据库初始化:db.sql 里的表关系与选课状态设计
3.1 六类核心表
这套选课系统的业务角色有三个:管理员、教师、学生。围绕这三个角色,数据库里至少要拆出六类表。以 db.sql 实际命名为准,常见结构是这样:
| 表 | 职责 | 关键字段 |
|---|---|---|
| admin | 管理员登录与权限 | username、password |
| major | 专业信息 | name |
| teacher | 教师信息 | name、major_id |
| student | 学生信息 | name、major_id、class_name |
| course | 课程信息 | name、teacher_id、capacity、credit |
| student_course | 学生选课记录 | student_id、course_id、status、grade |
专业表和教师、学生之间是一对多关系:一个专业下有多个教师、多个学生。课程表和教师也是一对多:一个教师可以教多门课。学生和课程之间是多对多关系,因为一个学生可以选多门课、一门课可以被多个学生选,多对多关系必须通过中间表student_course来解绑。
student_course是这套系统里最核心的一张表。它除了存student_id和course_id外,还必须有status和grade字段,这两个字段直接对应业务功能:教师审核选课、教师录入成绩。
3.2 用 Navicat11 导入 db.sql 并核对
环境版本对齐后,接下来就是把数据库初始化好。用 Navicat11 连上 MySQL 5.7,按下面四步走。
第一步,新建数据库,库名建议用student_course,字符集选utf8mb4,排序规则选utf8mb4_general_ci。第二步,选中这个新库,右键"运行 SQL 文件",找到压缩包里的 db.sql。第三步,执行完后按 F5 刷新,确认表结构已经生成。第四步,用下面这段 SQL 核对数据是否完整:
SHOW TABLES; -- 查看所有表是否齐全 SELECT COUNT(*) FROM student; -- 确认学生表有初始数据 SELECT COUNT(*) FROM course; -- 确认课程表有初始数据 SELECT COUNT(*) FROM student_course; -- 确认选课记录表有初始数据SHOW TABLES是最快的验证方式,如果执行后只有一两张表,说明 db.sql 没有完整导入。三个COUNT(*)分别检查业务数据是否写进去了,毕业设计项目一般会带几条测试数据,比如几个学生、几门课程,方便你启动后立刻演示。如果都是 0,说明脚本执行有问题,或者你运行错了文件。
3.3 选课记录表为什么必须带 status 字段
student_course表里的status字段是整个业务流程的枢纽。它的取值按常见设计一般是:PENDING待审核、APPROVED已通过、REJECTED已驳回、GRADED已打分。学生提交选课申请时记录是PENDING,教师审核通过后变成APPROVED,教师录入成绩后变成GRADED。
为什么不能省掉这个字段?因为系统的业务规则是"学生选课不等于选课成功",中间必须经过教师审核。如果没有 status,学生一选课就生效,教师的"审核选课信息"功能就成了摆设。这是这套系统区别于简单增删改查项目的关键点,也是答辩时最容易被问到的设计决策之一。
4. 后端与前端打通:登录鉴权、选课接口和 Vue 路由的协作方式
4.1 Controller-Service-Mapper 三层与角色判断
SpringBoot + MyBatis 项目的主流分层是 Controller、Service、Mapper 三层,这套选课系统也没跳出这个框架。Controller 负责接收前端请求和返回统一结果,Service 写业务规则,Mapper 用 XML 或注解写 SQL。
拿"学生查询自己已选课程"这个功能举例,Controller 层的代码大致长这样:
@RestController @RequestMapping("/api/student/course") public class StudentCourseController { @Autowired private StudentCourseService studentCourseService; @GetMapping("/list") public Result list(@RequestParam Integer studentId) { List<StudentCourseVO> list = studentCourseService.listByStudent(studentId); return Result.ok(list); } }@RestController声明这是一个返回 JSON 的接口类,@RequestMapping("/api/student/course")定义了接口前缀。list方法接收一个studentId参数,调用 Service 层查询,最终用Result.ok()统一包装返回。前端拿到这个 JSON 后,就能在"我的课表"页面渲染数据。
三层分离的好处是职责清晰:Controller 不写 SQL,Service 不处理 HTTP 请求,Mapper 只做数据访问。你改前端参数格式时只动 Controller,改业务规则时只动 Service,排查 SQL 问题时只翻 Mapper XML,不用在几百行的 Controller 里找SELECT语句。
4.2 选课接口:先查重再扣容量,最后留在待审核
选课是整个系统里业务逻辑最复杂的一个接口。常见实现是前端把studentId和courseId传到后端,Service 层做三件事:查重、校验容量、插入待审核记录。
核心逻辑我用伪代码拆给你看:
public boolean selectCourse(Long studentId, Long courseId) { // 第一步:查重,同一学生不能重复选同一门课 if (studentCourseMapper.countByStudentAndCourse(studentId, courseId) > 0) { throw new BizException("你已经选过这门课了"); } // 第二步:扣减容量,这里用条件更新而不是先查再改 int updated = courseMapper.increaseSelected(courseId); if (updated == 0) { throw new BizException("课程容量已满"); } // 第三步:插入选课记录,状态为待审核 return studentCourseMapper.insert(studentId, courseId, "PENDING") > 0; }这里最关键的 SQL 是容量扣减这一句,一般会写成条件 UPDATE:
UPDATE course SET selected_count = selected_count + 1 WHERE id = #{courseId} AND selected_count < capacity;selected_count < capacity这个条件就是防超选的保险。如果课程容量是 30,当前已选 30,这条 UPDATE 影响行数是 0,代码就会抛出"课程容量已满"的异常。如果先SELECT查容量再UPDATE更新,两个请求同时进来时会双双通过查询,导致超选。条件 UPDATE 通过数据库行锁避免了这个问题,这是和并发相关的经典踩坑点。
4.3 教师审核与成绩录入的状态流转
教师审核选课信息,本质上是把student_course表里某条记录的 status 从PENDING改成APPROVED。这里的 UPDATE 语句建议加上状态条件,防止重复审核:
UPDATE student_course SET status = 'APPROVED' WHERE id = #{recordId} AND status = 'PENDING';AND status = 'PENDING'的意思是:只有待审核的记录才能被更新成已通过。如果这条记录已经被审核过,影响行数就是 0,接口可以直接返回"该记录已被处理"。这个设计避免了教师连续点击两次按钮导致的状态覆盖。
成绩录入则是在审核通过的基础上,给选课记录写入分数并更新状态:
UPDATE student_course SET grade = #{grade}, status = 'GRADED' WHERE student_id = #{studentId} AND course_id = #{courseId};grade写入的是教师打的分值,status从APPROVED变成GRADED。到这里,一条选课记录的生命周期就走完了:学生提交(PENDING)→ 教师审核(APPROVED)→ 教师打分(GRADED)。学生端查询成绩时,前端拿到GRADED状态的记录才会展示分数。
4.4 Vue 端登录态与动态路由
前端 Vue 这边,axios 的请求拦截器是登录态管理的核心。它做的事情是:每次发请求前,从 localStorage 里取 token 塞进请求头,这样后端拦截器能识别出当前登录的是谁、是什么角色。
// src/utils/request.js 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'] = token } return config })baseURL: '/api'是接口前缀,和后端 Controller 里的@RequestMapping("/api/...")对应。timeout: 10000是 10 秒超时,请求太慢直接报错,避免页面一直转圈。token 放在请求头是前后端分离项目的通行做法,后端每次请求都能通过 token 判断用户身份,而不是依赖 session。
动态路由的常见实现是前端根据角色过滤菜单,而不是让所有按钮都渲染出来再根据权限隐藏。路由守卫伪代码如下:
const roleMenus = { admin: ['/admin/major', '/admin/teacher', '/admin/student', '/admin/course'], teacher: ['/teacher/audit', '/teacher/grade'], student: ['/student/select', '/student/course', '/student/score'] } router.beforeEach((to, from, next) => { const role = localStorage.getItem('role') if (to.meta.roles && !to.meta.roles.includes(role)) { next('/login') } else { next() } })roleMenus定义了每种角色能访问的路由列表,管理员管专业和账号,教师进审核和打分页面,学生进选课和成绩页面。beforeEach是 Vue Router 的全局前置守卫,每次路由跳转前都会执行。如果用户没有权限访问目标路由,直接踢回登录页。注意,前端路由守卫只是用户体验层面的拦截,后端每个接口还需要做真正的权限校验,不能只靠隐藏按钮。
前端开发环境下,Vue 和 SpringBoot 是分两个端口跑的,所以需要配代理解决跨域。打包部署时则把前端构建产物放到 SpringBoot 的src/main/resources/static目录,这样前端页面和后端接口同源,不需要跨域。发布到生产环境时,vue.config.js里有两个关键配置:
module.exports = { publicPath: './', outputDir: 'dist', devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }publicPath: './'是让打包出来的 JS 和 CSS 引用相对路径,否则部署到 SpringBoot 静态目录后会因为绝对路径找不到资源。devServer.proxy解决开发环境的跨域问题:前端跑在 8081,后端跑在 8080,代理把/api开头的请求转发到后端。changeOrigin: true修改请求头里的 Host,避免后端校验失败。
5. 避坑:编译、启动、选课与打包的五个高频问题
5.1 MySQL 8.0 引发驱动类名与连接报错
现象:项目启动时控制台报ClassNotFoundException: com.mysql.jdbc.Driver,或者报连接超时、Access denied 之类的问题,偶尔还会带出中文乱码。
原因:本机装的是 MySQL 8.0,而项目配置文件里写的是 MySQL 5.7 时代的驱动类名com.mysql.jdbc.Driver。MySQL 8.0 把驱动类换成了com.mysql.cj.jdbc.Driver,并且默认时区设置也变了。
解决:改 pom.xml 里的依赖版本,再改 application.yml 的数据库配置。核心是下面这两行:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/student_course?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghaidriver-class-name换成com.mysql.cj.jdbc.Driver是 MySQL 8 的标准驱动路径;url 里加了characterEncoding=utf8解决中文乱码,serverTimezone=Asia/Shanghai解决时区报错。如果你坚持用 MySQL 5.7,这串配置也能跑,只是cj驱动在 5.7 上也能兼容。
5.2 JDK 版本太高,SpringBoot 起不来
现象:启动 SpringBoot 时直接报UnsupportedClassVersionError,后面跟着 "compiled by a more recent version of the Java Runtime",或者 Lombok 相关的注解不生效。
原因:项目是按照 JDK 1.8 编译的,你本机用了 JDK 11 或 JDK 17 运行。SpringBoot 2.x 在 JDK 17 下容易遇到反射和模块访问限制,最常见的表现就是项目明明能编译,但启动到一半就崩。
解决:装一个 JDK 1.8,然后在 IDEA 里调整两处。File -> Project Structure -> Project SDK选 1.8;Run -> Edit Configurations -> JRE也选 1.8。如果同时装了多个 JDK,IDEA 的 Run Configuration 经常默认选最高的那个,不手动改就会踩坑。想改启动端口的话,也是在 Edit Configurations 的 Program arguments 里加--server.port=8081,或者直接改 application.yml。
5.3 npm install 卡住或 node-sass 编译失败
现象:前端执行npm install时长时间不动,或者装到一半报gyp ERR! build error,再或者提示node-sass需要 Python 环境。
原因:node 版本太高,和项目锁定的 node-sass 版本不兼容。node-sass 是一个原生模块,需要下载二进制文件本地编译,Node 版本一变它就要重新编译,经常卡在网络或权限上。
解决:固定 Node 版本到 14 或 16,然后换 npm 源重新安装:
npm config set registry https://registry.npmmirror.com rm -rf node_modules package-lock.json npm installregistry指向 npmmirror 是国内常用的加速源。rm -rf node_modules把之前装坏的依赖清掉,package-lock.json也删掉避免复用缓存里的旧依赖。重新npm install时如果还卡在 node-sass,就去检查 Node 版本,项目用 Vue CLI 4 或 5 的话,Node 14 是比较稳的选择。
5.4 打包后页面空白或静态资源 404
现象:前端npm run build之后,把 dist 目录的内容复制到 SpringBoot 的 static 目录,启动后端访问首页发现白屏,控制台报 CSS 或 JS 文件 404。
原因:Vue 打包出的 index.html 里引用的资源路径是绝对路径/static/css/app.xxx.css,部署到 SpringBoot 后,它会在项目根路径去找,找不到就白屏。另外,Vue Router 如果用了 history 模式,刷新子路由路径时后端没有对应的转发规则,也会 404。
解决:在vue.config.js里把publicPath改成'./',让打包后的资源引用变成相对路径。路由那边把 history 模式改成 hash 模式:
// router/index.js const router = createRouter({ history: createWebHashHistory(), routes })createWebHashHistory()是 Vue Router 4 的 hash 模式写法,它会在 URL 里加一个#,比如/#/student/course。这样刷新页面时请求的始终是根路径,不会再出现子路由刷新 404。毕业设计演示场景下,hash 模式比 history 模式省事得多。
5.5 数据库中文乱码
现象:往数据库里插入学生姓名、课程名称时,页面上显示正常,但数据库里存的是???,或者 Navicat 里看表数据全是问号。
原因:建库时字符集不是 utf8mb4,或者连接串里没指定 characterEncoding。MySQL 默认的 latin1 不支持中文,插入时中文字符被转成了问号。
解决:库和表都用 utf8mb4,连接串加参数。已经建错的库可以执行这条语句修正:
ALTER DATABASE student_course CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;ALTER DATABASE只改默认字符集,对已存在的表还需要逐个ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4。我在导入 db.sql 之前一定会先确认建库语句里带没带 utf8mb4,不带就先手动建库再导 SQL,别偷懒。
6. 进阶:用一条完整链路验证选课系统能不能撑住答辩
6.1 造一批能讲出故事的演示数据
很多同学答辩翻车,不是系统有问题,而是演示数据太弱。默认的 sql 里那几条数据,一选课就超容量,或者页面空空荡荡,根本展示不出系统的业务闭环。我的习惯是启动前手工造一批有层次的数据:两个教师,一个教公共必修课,一个教专业选修课;八门课程,其中一门容量设为 1 但里面已经有 1 人选了,用来演示"选课失败"分支;三个学生,其中一个已经选了五门课达到上限,用来演示查重拦截。
这样的数据设计让每一个按钮都有故事可讲。你演示的不是普通的增删改查,而是"选课成功、重复选课被拦、容量已满、教师审核通过、教师驳回、成绩录入"这六种真实场景。答辩时评委想看的就是这种边界情况你怎么处理。
6.2 按答辩顺序走一遍完整链路
选课系统的业务闭环是:学生选课 → 教师审核 → 教师打分 → 学生查成绩。我每次拿到这套源码,都会按这个顺序手动走一遍,同时盯着数据库里student_course表的变化:
| 操作 | 页面 | 预期数据库变化 |
|---|---|---|
| 学生登录并选择一门课 | /student/select | student_course 新增一条记录,status = PENDING |
| 教师登录并审核该选课 | /teacher/audit | status 从 PENDING 变为 APPROVED |
| 教师录入成绩 | /teacher/grade | grade 字段写入分数,status 变为 GRADED |
| 学生查看成绩 | /student/score | 页面展示该课程分数 |
走完这一圈,用下面这句 SQL 做最终验证:
SELECT student_id, course_id, status, grade FROM student_course ORDER BY id DESC;把查询结果和前端页面比对,status 字段是否按预期流转、grade 是否写入,一目了然。这一步值得在答辩前一晚跑一次,页面和数据对上了,第二天才不慌。
6.3 答辩时值得展开讲的三个设计点
评委大概率会问三个问题:为什么选课记录要设计 status 状态机、选课容量怎么防超选、前端角色路由怎么控制权限。这三个点对应的是状态设计、并发控制、前后端协作,每个都能展开讲两分钟。我一般会从状态机讲起,因为它是整个业务流程的骨架,容量防超选是亮点,路由权限是加分项。把这三个点讲清楚,答辩基本稳了。
我最早拿到这类带 bat 的源码时,习惯跳过 install 直接双击 run.bat,结果数据库配置没就位,报错报了一下午。从那以后我每次跑 SpringBoot 项目都强制走同一套流程:版本对齐 → 导入 db.sql → mvn clean install → 启动后端 → 验证选课链路,整套通了再去动代码。希望帮到你。
本文还有配套的精品资源,点击获取