
简介一份面向计算机类毕业设计/课程设计的学生选课系统完整源码包基于SpringBootVueMySQL实现前后端分离。系统覆盖管理员、教师、学生三类角色管理员可维护专业、教师、学生和课程信息查看选课情况与成绩教师能查询学生与课程、审核学生选课并为课程打分学生则支持选课、查询已选课程及成绩功能贴合高校教学管理场景适合需要可运行项目参考的高校学生和Java后端学习者。包内共126个文件以97个Java源码为核心搭配Vue前端JS/CSS、SpringBoot配置YML/XML、MyBatis映射及SQL初始化脚本同时提供一键部署bat脚本、项目说明文档、开题报告和PPT答辩材料压缩包仅3.83MB目录按后端、前端、数据库、文档分层结构清晰按模块阅读和二次开发都很方便。已有61人学习下载。借助完整前后端代码、数据库脚本和部署文档可快速在JDK1.8MySQL5.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.7Maven 用的是 3.3。你要是机器上装了 JDK 17 或者 MySQL 8.0后面每一步都会出幺蛾子。我一般会先按下面这张表把本机环境对齐再往下走软件本项目要求的版本为什么不能随便升级JDK1.8SpringBoot 早期版本基于 Java 8 编译JDK 11/17 下常见 javax 包缺失、Lombok 失效MySQL5.7db.sql 里的建表语句与 5.7 的 sql_mode 匹配MySQL 8 的驱动类名和认证方式都变了Maven3.3项目里有 mvnw.cmdwrapper 会优先使用项目绑定的 Maven 版本减少本机差异Navicat11连接 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.x8.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、passwordmajor专业信息nameteacher教师信息name、major_idstudent学生信息name、major_id、class_namecourse课程信息name、teacher_id、capacity、creditstudent_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) { ListStudentCourseVO list studentCourseService.listByStudent(studentId); return Result.ok(list); } }RestController声明这是一个返回 JSON 的接口类RequestMapping(/api/student/course)定义了接口前缀。list方法接收一个studentId参数调用 Service 层查询最终用Result.ok()统一包装返回。前端拿到这个 JSON 后就能在我的课表页面渲染数据。三层分离的好处是职责清晰Controller 不写 SQLService 不处理 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 是容量扣减这一句一般会写成条件 UPDATEUPDATE 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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghaidriver-class-name换成com.mysql.cj.jdbc.Driver是 MySQL 8 的标准驱动路径url 里加了characterEncodingutf8解决中文乱码serverTimezoneAsia/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.8Run - Edit Configurations - JRE也选 1.8。如果同时装了多个 JDKIDEA 的 Run Configuration 经常默认选最高的那个不手动改就会踩坑。想改启动端口的话也是在 Edit Configurations 的 Program arguments 里加--server.port8081或者直接改 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/selectstudent_course 新增一条记录status PENDING教师登录并审核该选课/teacher/auditstatus 从 PENDING 变为 APPROVED教师录入成绩/teacher/gradegrade 字段写入分数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 → 启动后端 → 验证选课链路整套通了再去动代码。希望帮到你。本文还有配套的精品资源点击获取