1. 项目到底在做什么:选课系统的完整需求链路
做这个毕业设计前,我建议你先别急着敲代码,把"学生网上选课系统"这几个字拆开看一遍。表面上是三个词:SpringBoot、Vue、MySQL,本质上是"高校教学管理中最常见的一块业务场景"。选课系统解决的是三个角色的三类问题:学生要查课、选课、退课、看课表;教师要看自己教什么课、维护成绩;管理员要管学生、教师、课程、选课名额、发布公告。这三条线串起来,就是一套典型的、完整的、能讲清楚业务逻辑的CRUD项目。
很多人觉得这类题目老套,但老套恰恰是毕业设计的优势。评审老师看中的不是你的技术栈有多新,而是你对一个完整业务系统的理解和落地能力。SpringBoot负责后端接口和事务处理,Vue负责前端交互和页面渲染,MySQL负责数据持久化,三者配合能说清楚"请求是怎么一步步从浏览器走到数据库再走回来的"——这就是答辩时最值钱的部分。
从实际开发角度看,这个项目的核心难点其实不在增删改查,而在几个容易被忽视的细节:选课冲突检测、并发情况下名额不超卖、登录认证和权限控制、前后端接口约定。这些点做好了,项目从"能用"变成"有深度",打分和答辩都会顺很多。这篇文章我会按照我自己做这类项目的经验,给你把整个链路从数据库设计到部署上线拆一遍,最后附上我踩过的坑和排查思路。
如果你是准备拿这套系统做毕业设计或者课程设计的,这篇文章可以直接当你的开发参考文档用。我不讲虚的,全部是能落地的方案和代码思路。
2. 技术选型解析:为什么 SpringBoot+Vue+MySQL 是稳妥组合
2.1 后端为什么选 SpringBoot 而不是 SSM
现在很多高校的课程还在教 SSM(Spring + SpringMVC + MyBatis),但到了做毕设的时候,我强烈建议你直接用 SpringBoot。原因很简单:SSM 的配置太繁琐,一个 XML 配置文件写错就能卡你一整天,而 SpringBoot 通过自动配置把这些东西大部分搞定了,你只需要关注业务代码本身。
从另一个角度讲,SpringBoot 的生态成熟,网上资料多,出了问题能很快搜到解决方案。用 SpringBoot 做选课系统,核心依赖无非就是 spring-boot-starter-web、spring-boot-starter-security 或拦截器方案、MyBatis-Plus(或 JPA)、MySQL 驱动这些。起步快,而且框架本身的"约定优于配置"理念在写论文时也很好展开。
在这个项目里,我的建议是:业务复杂度不高,但权限和事务逻辑要讲清楚。所以后端分层可以按 Controller、Service、Mapper(或 Repository)来,Controller 只做参数接收和结果封装,业务逻辑全部放 Service,数据库操作放 Mapper。这样的分层在论文的结构设计上也很清晰,画系统架构图、时序图都有现成依据。
2.2 前端为什么用 Vue 而不是传统 JSP
上一届可能还有人用 JSP 做选课系统,每张页面都是一个 Java 文件,前后端混在一起,改一个按钮样式都要重启服务器。Vue 的核心价值在于前后端分离:前端工程和后端工程可以独立开发、独立部署,前端通过 HTTP 请求调用后端接口,数据双向绑定让页面交互的代码量减少一个量级。
具体到选课系统,Vue 的优势非常明显。比如课表展示,你需要根据课程时间和节次动态渲染一个 7 列、12 行的表格;用原生 JS 你要手动操作 DOM,用 Vue 就用一个数组的数据源加 v-for 循环就能解决。再比如选课时的实时搜索和筛选,computed 属性一句话搞定,不需要写一堆事件监听。
前端方面我用的是 Vue 2 + Element UI(如果你现在新建项目,也可以用 Vue 3 + Element Plus),搭配 Vue Router 做页面跳转、Axios 做接口请求。这套组合的资料非常多,遇到组件不会用、路由配置报错这类问题,随便一搜都有答案,非常适合需要快速出活的项目场景。
2.3 MySQL 的角色与数据持久化设计思路
MySQL 在这个项目里负责把所有业务数据落地。学生信息、教师信息、课程信息、选课记录、公告内容、用户账号,全部要落到表里。设计的关键是:表结构要能支撑业务逻辑,而不是简单地东放一个字段西放一个字段。
选课系统最大的一个坑是"课程名额"和"已选人数"这两个字段怎么管理。很多同学会直接在课程表里存一个 remaining(剩余名额)字段,每次选课成功就减一。思路没错,但并发情况下会出问题。更稳妥的做法是:课程表只存课程总容量,已选人数通过统计选课记录表来实时计算。虽然这样多一句统计查询,但数据的一致性更好。你要是想把性能优化一下,可以再加缓存或者用数据库事务锁,这些在答辩时都是加分点,我在后面第 3 章详细讲。
3. 数据库设计:选课系统的地基怎么搭
3.1 核心表结构与字段说明
我先给出一个基于实际开发经验整理的表结构方案,一共六张核心表,已经能覆盖选课系统的主要业务场景。
用户表(sys_user),建议把学生、教师、管理员统一放在一张表里,通过 role 字段区分角色。这样可以复用一套登录逻辑,不用为三种角色写三套用户校验。字段包括:id、username、password、real_name、role、phone、email、created_time。
学生信息表(student_info),与用户表一对一关联,存放学号、班级、年级、专业、院系等信息。可能有人问为什么不能全放用户表里?因为学生有大量专属信息,单独拆表更清晰。
教师信息表(teacher_info),与用户表一对一关联,存放工号、职称、所属院系。
课程表(course_info),字段包括:id、course_code(课程编号)、course_name、teacher_id(授课教师)、credit(学分)、total_capacity(总容量)、classroom、course_time(上课时间)、week_info(上课周次)、course_desc。
选课记录表(student_course),这是整个系统最核心的业务表,字段包括:id、student_id、course_id、selected_time、status(正常/退课)。注意,同一学生同一门课只能有一条有效记录,这一点在数据库层面加唯一约束,别只靠代码判断。
公告表(notice):id、title、content、publisher_id、publish_time。
除这六张外,还可以加一个课程选课时间配置表,用来控制"开放选课"和"关闭选课"的窗口期,这个字段我建议加上,因为现实中选课一定是有时间限制的,答辩时老师也可能问到"系统怎么控制选课时间"。
3.2 外键与关联关系怎么设计才合理
我见过不少同学为了图省事,把关联字段全部做成普通字段,比如课程表里直接存 teacher_name,不存 teacher_id。这种做法在系统刚启动时看着挺方便,但后来你要改教师姓名、查教师开课列表时,就会陷入数据不一致的麻烦。
正确做法是:业务表之间用外键 ID 关联,展示时需要名称用联表查询或者 VO 类去组装。比如选课记录表只存 student_id 和 course_id,你想在前端显示学生姓名和课程名,后端用联表查询把数据补齐再返回,而不是在选课记录表里冗余一份姓名和课程名。主外键关系上:
- 用户表与角色相关,一个用户对应一条学生或教师信息
- 教师与课程是一对多关系,一个教师可教多门课
- 学生与课程是多对多关系,通过选课记录表关联
- 公告发布者是用户,公告与用户多对一
这样的结构在画 ER 图时非常规整,论文里的数据库设计章节可以直接用。
3.3 约束、索引与初始化数据的注意点
建表时加约束不是小事。选课记录表上我给两个建议:一是(student_id, course_id)加唯一索引,防止同一学生对同一门课的重复记录;二是为课程表、学生表、教师表的常用查询字段建普通索引,比如课程名模糊查询、教师 ID 查询课程等。数据量在毕业设计阶段不大,索引效果不明显,但这个点写进论文说明"你考虑了查询性能",是加分项。
初始化数据这块,很多同学直接手动往数据库插假数据,浪费大量时间。我的做法是写一个 SQL 脚本文件,一次性插入 50 个学生、10 个教师、20 门课程、若干选课记录和公告。注意密码字段不要存明文,用 MD5 或 BCrypt 加密后的字符串。这个脚本最后放进项目根目录,部署文档里说明执行一下就能跑起来。
4. 后端核心逻辑:认证、选课与事务保障
4.1 登录认证与权限拦截怎么做
既然用户表统一用 role 字段区分角色,登录接口就可以共用一套逻辑:前端传用户名和密码,后端校验用户是否存在、密码是否正确,成功后按角色返回不同的菜单权限和首页数据。这里的关键是用拦截器(Interceptor)或 Spring Security + JWT 来管理登录状态。
我个人的建议是,如果你熟悉 Spring Security,可以上 Security + JWT;如果不熟,用拦截器 + Token 会更容易控制,也更容易在论文的"系统实现"部分写清楚。具体流程:
- 登录成功后,后端生成一个 Token(可以是 UUID 或者 JWT)。
- 前端把 Token 存到 localStorage,每次请求在 Header 里带上。
- 后端写一个拦截器,放行登录接口,其余接口统一校验 Token。
- 校验通过后把用户 ID 和角色放进请求上下文,供后续业务使用。
4.2 选课接口的设计与并发防超卖
这是全项目最核心的代码段落。先明确业务规则:学生选课时,要检查课程是否已存在(同一门课不重复选)、课程容量是否已满、选课时间是否开放。伪代码逻辑如下:
- 查询选课记录是否已存在,存在则抛出"不能重复选课"异常
- 查询课程表,判断当前时间是否在选课窗口内
- 统计(不是读取已选数字段)该课程的有效选课记录数
- 若已选人数小于总容量,则插入选课记录
问题在于:如果 100 个学生同时点选课,都通过了第 3 步的统计判断,然后一起插入记录,就可能导致实际选了 101 人。解决的办法我建议用 MySQL 的行锁或乐观锁。课程表上加一个 version 字段(乐观锁),选课操作通过 SQL 来执行"更新课程表 set version = version + 1 where id = ? and version = 旧值",更新受影响行数为 0 就说明课程被其他事务改了,需要重新判断。
用 MyBatis 实现乐观锁的逻辑类似这样:先查课程,拿到 version;执行更新时把 version 作为条件;更新成功后检查返回值,若为 0 则回滚并提示"选课人数已满"。
这里我要强调:不要把已选人数直接减容量当成唯一判断手段,并发场景下必有超卖风险。有同学问我用 synchronized 行不行,那只是单机版有效,如果以后做分布式部署就不行了。从论文角度讲,写"基于乐观锁的并发控制方案"比写"加了把锁"要高大上很多。
4.3 退课、成绩录入与统计
退课逻辑相对简单:把对应的选课记录状态置为退课,或者物理删除。我建议用状态字段做逻辑删除,因为选了又退是正常教学行为,保留记录可以统计分析选课热度。
成绩录入是教师角色的功能。教师只能看到自己担任教师的课程、选课学生列表,录入成绩后写回选课记录表的 score 字段。这里要注意权限校验:不能只在前端隐藏按钮,后端接口也必须校验当前登录用户的 teacher_id 和课程 teacher_id 是否一致。
统计功能主要面向管理员,比如每门课的选课人数、热门课程 TOP10、学生选课学分汇总。这些统计可以用聚合查询实现,不用额外建表,只要 SQL 写得对就行。
5. 前端 Vue 实现:从页面到交互细节
5.1 页面结构与路由规划
前端按角色分三套主页面:学生端、教师端、管理端。学生端包含选课中心、我的课表、已选课程、成绩查询、个人中心;教师端包含我的课程、学生名单、成绩录入、个人中心;管理端包含学生管理、教师管理、课程管理、公告管理、选课统计。
Vue Router 里可以用路由守卫判断用户角色,未登录跳转到登录页,非对应角色访问页面时拦截重定向。具体实现是在登录成功后把 role 存到 Vuex 或本地,路由 beforeEach 里做判断。
5.2 Axios 封装与前后端接口约定
前端与后端联调的基础是统一约定的接口格式。我建议所有接口返回统一 JSON 结构:{ code: 200, message: "success", data: {...} }。前端在 Axios 的响应拦截器里判断 code,200 则直接取 data,非 200 则弹出提示。
Axios 实例的封装要考虑三件事:baseURL 统一指向后端服务地址、请求头自动带上 Token、401 响应时自动跳转登录页。很多同学在联调时报跨域、报 Token 丢失,基本都能在这三个地方找到原因。
接口命名建议按模块划分,比如/api/auth/login、/api/student/course/list、/api/student/course/select、/api/teacher/course/list、/api/admin/course/save。前后端接口文档可以简单写一个 Markdown 表格,包含接口路径、请求方式、参数说明、返回结构,这份文档在论文附录里也很实用。
5.3 关键交互组件:选课列表与课表展示
选课中心的页面是学生最常用的。我设计的交互是:顶部搜索框支持课程名和课程编号模糊搜索,列表左侧展示课程卡片,右侧展示课程详情和选课按钮。选课成功后按钮变灰并显示"已选"。
课表展示是另一个难点。前端拿到课程时间字段(比如"周一 1-2 节"),需要解析成星期和节次,然后渲染到表格里。我的做法是后端直接返回两个字段:weekday(1-7) 和 section(如[1,2]),前端根据这两个字段定位到表格的行列,合并单元格渲染课程名和教室。注意解析课程时间字符串的工作尽量放在后端做,前端拿到结构化字段直接渲染,减少前端判断逻辑。
6. 部署全流程实操:从源码到能跑起来
6.1 本地开发环境配置
本地开发我建议的配置是:JDK 1.8(或 11)、Maven 3.6+、Node.js 14+(Vue2 项目)、MySQL 5.7 或 8.0、IDEA 和 VSCode。前端和后端同时启两个服务,后端默认 8080 端口,前端默认 8081 或 9528(Vue CLI 默认)。
为了联调不出跨域问题,我强烈建议在 Vue 项目里配置开发代理,vue.config.js里把/api前缀的请求代理到http://localhost:8080。这样前端请求看起来是同源的,不会触发浏览器的跨域策略,也不用在后端单独配 CORS 过滤器的宽松策略。
6.2 前后端打包与生产部署
后端打包用 Maven,在项目根目录执行mvn clean package -DskipTests,得到 jar 包。前端打包用npm run build,生成 dist 目录。生产部署有两种常见方式:一是前后端分开部署,后端 jar 放服务器跑,前端 dist 用 Nginx 托管;二是把前端 dist 里的静态文件复制到后端项目的 static 目录下,打包成一个 jar 一起启动。
我推荐第二种方式,因为它对不了解运维的学生来说更省事。具体操作:把前端 build 出来的 static 和 index.html 放到 SpringBoot 的src/main/resources/static目录,重新打包启动,访问http://IP:8080就能看到整个系统。后端接口仍然走/api前缀,不需要额外配置静态资源映射。
6.3 部署清单与验证步骤
部署完成后,至少要做一轮完整验证:访问首页能否打开、登录三种角色账号是否正常、学生能否选课和退课、教师能否录入成绩、管理员能否新增课程和公告、刷新页面后登录状态是否保持、数据库里的数据是否正确落库。建议把这些验证步骤写进部署文档的验收表里,方便自己排查,答辩时也可以展示。
7. 实测中常见的 Bug 与排查技巧
7.1 几个典型的开发阶段问题
第一,选课提示"课程已满"但数据库里明明有空位。多半是并发控制逻辑写错,比如用了课程表的已选数字段而没有加锁,导致高并发测试时更新掉了彼此的数据。排查时先看日志有没有回滚记录,再用两个账号同时操作同一门课复现。
第二,前端调用接口报 401 或 Token 无效,前端明明登录成功了。先看请求头里有没有带 Authorization,再看后端拦截器是否放行了登录接口和静态资源路径,最后看 Token 的过期时间设置是不是太短。
第三,课表显示错位,课程跑到错误的节次。这个是解析课程时间字段的规则没统一,你在后端存的是什么格式,返回给前端的是什么结构,必须先用接口文档约定清楚,别前端写一套解析后端写一套。
7.2 选课并发场景怎么自测
很多同学想知道自己的并发控制到底行不行。一个简单的方式:用 Postman 或 JMeter 模拟多个并发请求,同时调用选课接口。课程容量设置成 5,发起 10 个并发选课请求,看最终选课记录数量是不是 5。如果超过 5,说明控制逻辑有问题;正好 5,则说明乐观锁或排他锁生效了。
这一步很重要。答辩时你讲"我考虑了并发选课风险",有测试数据支撑比空口说更有说服力。
7.3 论文与答辩的准备思路
论文的框架建议按摘要、绪论、需求分析、系统设计、系统实现、系统测试、总结来写。在需求分析阶段,把三种角色的用例图画清楚;系统设计阶段,把总体架构图、功能结构图、E-R 图放进去;系统测试阶段,列功能测试用例表,务必包含并发选课的测试场景。
答辩讲解的思路也有讲究,别一上来就讲代码,按"需求和痛点 -> 整体设计 -> 核心难题(并发/权限/时间窗口) -> 实现验证"的顺序来组织。讲清楚为什么要用乐观锁控制超卖,比背一百句 SpringBoot 的优点都管用。
8. 按我的经验,做这类项目最该抓住的几个点
这个选题做下来,我自己最大的体会是:选课系统的难点不在代码量,而在几个关键业务的闭环。你把"选课不超卖"、"退课后名额自动释放"、"不同角色只能操作自己的数据"这三件事想透了,系统的质量立刻上一个档次。
如果你打算基于这个题继续扩展,我建议下一步可以考虑加缓存,把热门课程的信息放到 Redis 里,减轻数据库压力;也可以加定时任务,到选课窗口关闭时间自动触发一论课程分配,把数据整理的逻辑再往深处挖一挖。这些扩展点写进论文的展望部分,会让整篇文章的深度看起来更好。
最后再多说一句,做毕业设计不是比谁的技术名词多,而是把你实际做完的东西讲明白。代码可以抄,思路抄不走。照着这篇文章把每一步的"为什么"想一遍,答辩时你就不会再怕老师追问了。