SpringBoot+Vue学生选课系统实战:从数据库设计到并发控制

SpringBoot+Vue学生选课系统实战:从数据库设计到并发控制 SpringBootVue学生选课系统这个课题在计算机毕业设计里出镜率一直很高。不是因为它特别炫技而是因为它覆盖面够广、业务逻辑够典型、工作量适中后端有SpringBoot撑场子前端有Vue负责交互数据库设计还能体现关系型数据建模的功底一套下来基本能看出一个学生有没有完整走通一个Web项目的全流程。如果你正在纠结选题或者已经选了类似题目但不知道从哪下手这篇内容会从选题逻辑、系统设计、核心代码落地到论文整理讲清楚这个系统到底怎么做哪些地方容易踩坑以及怎么把“能跑”的代码变成“能答辩”的项目。1. 系统整体设计与需求拆解先别急着写代码第一步是把需求捋清楚。选课系统的核心业务场景并不复杂但涉及的角色和状态流转如果没想明白后面写接口的时候一定乱。1.1 角色模型与权限边界这套系统里至少有三类角色学生、教师、管理员。学生要能登录、看课程列表、选课、退课、查成绩教师要能维护自己的课程信息、查看选课名单、录入成绩管理员则负责基础数据的管理比如学生信息维护、教师账号分配、课程审核、整体数据统计。权限边界不清晰是很多毕设项目被老师问倒的重灾区所以在设计阶段就需要明确学生不能创建课程教师不能修改其他教师的课程管理员不参与具体选课业务。后端接口必须做角色校验而不是单纯在前端隐藏按钮就完事。1.2 业务流程图背后隐藏的状态机选课业务有几种典型状态需要提前设计好。课程有可选、已选满、选课中、选课截止、已结课等状态学生的选课记录有已选、已退、课程冲突等状态成绩有未录入、已录入、已发布等阶段。状态切换用数字枚举存数据库是目前主流做法但前端的展示需要将其映射为中文标签。比如status 1表示可选status 2表示已满前端在渲染课程卡片时可以直接判断状态禁用按钮。这里有个容易忽略的点退课后的名额释放。如果一门课已经选满有学生退课系统应该第一时间把名额释放出来。很多人会把这条逻辑放到业务代码里手动处理但如果退课操作和选课操作并发发生就存在超选风险。这个问题在后面的具体实现里会专门说提前在这里意识到它的存在后面设计接口时就会多留意。1.3 为什么选SpringBootVue这套组合毕业设计选型求的不是“最炫”而是“最稳”。SpringBoot的核心价值在于几乎零配置就能启动一个Web服务内嵌Tomcat不需要单独部署容器这对很多不熟悉服务器部署的学生来说省了一大半心力。配合MyBatis Plus增删改查的常规操作基本不用写SQL数据访问层开发效率极高。前端选择Vue框架本身学习曲线平缓组件化开发思路清晰。配合Element Plus组件库表格、表单、对话框这些后台管理界面高频组件能直接复用几张核心页面半天时间就能搭出雏形。如果前后端分离部署时前端打包成静态文件丢进Nginx或者直接扔到SpringBoot的static目录里都能在答辩现场做到快速演示。2. 数据库设计这个系统的灵魂所在很多人写功能代码很快一到建表就开始乱。选课系统数据量虽然不大表之间的外键关系、字段约束逻辑却是面试官和评阅老师重点看的部分。这一块成熟方案已经非常固定核心表就五张搞清楚关系即可。2.1 核心数据表结构与关系学生表student和用户表user以及教师表teacher可以合也可以分。建议用户表单独建只存账号、密码、角色学生和教师通过user_id关联到各自的扩展信息表这样登录认证逻辑统一后续扩展功能会非常省事。课程表course字段建议包含course_id课程编号、course_name课程名称、teacher_id授课教师ID、credit学分、course_time上课时间、course_place上课地点、selected_count已选人数、max_count容量上限、status课程状态。course_time建议用字符串存储例如周一 3-4节虽然不够范化但毕设答辩完全够用复杂度大幅降低。选课记录表student_course是核心业务表包含id、student_id、course_id、select_time、status。为什么单独建表而不是在学生表里加一个课程字段因为学生和课程是多对多关系一门课有很多学生选一个学生也可以选很多课程不建中间表关系就乱了。成绩数据可以直接挂在选课记录表上增加score字段即可毕竟成绩一定是针对“某个学生选某门课”这个事实来记录的。2.2 字段类型与约束设计的细节主键建议使用雪花算法生成的分布式ID或者数据库自增ID都行。毕设规模用自增ID最简单明了BIGINT类型保证不溢出。学生学号、教师工号这类业务编码用VARCHAR存单独做唯一索引。状态类字段统一用TINYINT存储数值而不是字符串这样查询效率高语义上也更规范。有一个容易忽略的字段课程的selected_count。这个字段属于冗余设计目的就是避免每次选课都要COUNT(*)去统计选课记录表。但冗余字段必须小心维护学生选课成功要加一退课成功要减一如果这两个操作放在事务里执行数据一致性就有保障。不建事务的话并发情况下这个数字会漂移后面专门讲并发控制的时候会细说。2.3 初始化数据的坑开发阶段最容易被卡住的就是数据初始化。系统启动连不上数据库、没有默认管理员账号等问题非常普遍。建议准备一份init.sql脚本把数据库建表语句和预置数据放一起。默认账号密码前后端联调时要用管理员、教师、学生至少各造一个造完写进项目README不然半个月后自己都忘了密码是什么。3. 后端业务逻辑与关键代码实现后端工程结构建议按模块分包controller、service、mapper、entity、common、config。这种结构每层职责单一排查问题的时候能快速定位到对应位置。3.1 SpringBoot项目初始化与核心依赖创建项目推荐直接用Spring InitializrGroup填com.exampleArtifact填course-selection。核心依赖加上spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、spring-boot-starter-validation。连数据库的信息写进application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto注意serverTimezone必须配不配的话高版本MySQL驱动连接时会报时区错误这个错很多人遇到过。log-impl配成StdOutImpl的好处是开发阶段能在控制台直接看到SQL联调排错能省不少时间部署到生产环境再删掉就行。3.2 登录鉴权的方案选型登录认证的做法从最简单的Session到JWT到Spring SecurityOAuth2方案很多。毕设建议直接用JWT代码量小且逻辑清晰答辩时还能讲清楚“无状态认证”的概念。实现思路是用户登录成功后后端用jjwt库生成一个Token返回给前端。前端拿到Token存储在localStorage里每次请求在请求头加上Authorization: Bearer token。后端的拦截器统一校验Token解析出用户ID和角色放到ThreadLocal里供业务方法随时取用。Token的过期时间建议设置成24小时太短的话演示时不停重新登录会很尴尬太长又有安全隐患一个白天的时间在毕设场景下刚好合适。JWT密钥写死在配置文件里可以但答辩时如果有老师问到安全性能答出来“实际生产环境会放到配置中心或环境变量”会加分不少。3.3 学生选课的并发问题与事务控制这是整套系统最有技术含量的一个点也是答辩时老师大概率会追问的地方。学生选课的接口逻辑如果是先查询课程是否满员未满则插入选课记录更新已选人数。直接这么写在并发量小的时候没问题但一旦两个学生同时抢最后一门课时可能出现两个人都查到“只剩1个名额”然后都插入成功结果选课人数超过容量上限。解决思路是给course表增加乐观锁字段version用MyBatis Plus的Version注解标记更新时带上版本号判断Update(UPDATE course SET selected_count selected_count 1, version version 1 WHERE course_id #{courseId} AND version #{version} AND selected_count max_count) int deductStock(Param(courseId) Long courseId, Param(version) Integer version);这句SQL自带selected_count max_count条件如果影响行数为0说明课程已经满了或版本冲突直接抛出业务异常提示“选课人数已满”。整个选课操作加上Transactional事务注解任何一步抛异常都会回滚保证数据不会写到一半卡住。同样的思路处理退课更新时判断selected_count 0后再减一整个数据库操作处于一个完整事务中连本地测试都不会出现脏数据。3.4 课程冲突判断其实很简单选课还有个隐藏逻辑同一时间不能选两门课。实现方式是在插入选课记录前查出该学生已有的选课记录逐条对比上课时间是否重叠。如果course_time用类似周一 3-4节的字符串存储冲突判断就直接比较字符串相等即可。同一个学生选了周一 3-4节的数据结构课就不能再选周一 3-4节的数据库原理课。考虑到毕设的数据规模和业务复杂度字符串相等的判断已经足够强行做一个复杂的”星期节次单双周”建模只会把代码搞复杂答辩时还得花额外精力去向老师解释。3.5 教师成绩录入的权限隔离成绩录入接口要确认当前登录用户是教师角色并且该教师确实教这门课。接口设计时前端传入courseId和studentId后端在Service层先判断课程归属再执行更新操作。如果直接把成绩更新的SQL暴露出去不限权限任何人都能调用接口改成绩这属于很严重的越权漏洞设计文档里写清楚这一点在答辩时是明显的加分项。成绩录入后还要考虑状态流转课程未结束时教师应该不能录成绩学生也未到查看成绩的时间。用课程表里的status字段做流转控制即可状态机画到论文里逻辑一目了然。4. 前端功能模块与Vue实现前端部分重点不在页面多好看而在于路由划分、状态管理、交互流程是否清晰。Vue3 Vite Element Plus这套组合是目前主流方案创建项目用npm create vuelatest按提示选择需要的配置项即可。4.1 前端项目初始化要点安装依赖时注意网络问题npm install经常卡住可以用淘宝镜像源加速npm config set registry https://registry.npmmirror.com npm install项目创建完成后目录结构建议保持Vue Router自动生成的结构views放页面组件router放路由配置store放Pinia状态管理api放Axios请求封装。Axios封装时统一设置baseURL为/api并在请求拦截器里自动放入Tokenimport axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) export default request4.2 前端路由与权限控制前端路由必须做权限控制典型方式是路由的meta字段里写上能访问的角色数组配合Vue Router的beforeEach守卫做拦截判断router.beforeEach((to, from, next) { const role localStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) { next(/login) return } next() })这样做有一个容易被忽略的细节问题前端守卫只是体验优化不是真正的安全屏障。任何一个懂技术的人打开浏览器控制台都能看到真实接口地址直接调用接口就能绕过页面限制。所以防越权的底线必须在后端做这一点写论文时单独拎出来讲能让老师觉得你是真思考过安全问题。4.3 学生选课页面的交互设计选课页面是整套系统前端工程量最大的部分。页面布局建议左侧课程筛选区右侧课程列表。课程列表用Element Plus的el-table渲染已选课程和可选课程用标签区分超出容量的课程对应的选课按钮禁用。用户选课成功和失败的提示方式建议用ElMessage组件弹出轻提示例如“选课成功”用绿色提示“选课人数已满”用红色提示。这样比弹窗轻量展示体验也更自然。后端返回的数据结构统一设计为{ code: 200, message: success, data: { courseId: 1, courseName: 数据结构, teacherName: 王老师, credit: 4.0, selectedCount: 30, maxCount: 40, status: 1 } }前端拿到data后直接渲染不用做二次包装代码会非常干净。4.4 成绩查看与可视化成绩页面用el-table展示各门课程成绩、学分、绩点顶部放统计卡片显示总学分和平均绩点。有条件的话可以用ECharts做一个简单的柱状图展示成绩分布虽然实现成本不高但视觉效果很加印象分。一个实际项目里的细节后端返回成绩列表时如果score字段为NULL表示成绩未录入前端应该显示“未录入”而不是直接渲染空白或者0分。这个字段处理看起来简单但不处理就会出现满屏的空白行老师看到会觉得很毛躁。5. 前后端联调与本地部署全流程前后端分离的项目联调环节是真正耗费时间之处踩坑频率最高的也集中在这。5.1 解决跨域问题前端开发服务器默认端口是5173后端接口是8080直接请求一定报跨域。解决方式推荐在后端统一配置跨域过滤器而不是在前端开代理。开发环境下后端配置一次所有接口都通了Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }一个需要注意的点是如果后端开启了JWT拦截器跨域预检请求OPTIONS会先到达后端。拦截器要先将OPTIONS请求直接放行否则前端会一直报CORS错误而实际后台接口是正常的。5.2 前端静态资源打包与合并部署开发完成后前端执行npm run build生成dist目录。两种部署方式任选第一种是Nginx部署配置server块将location /指向dist目录location /api/反向代理到http://localhost:8080。毕设演示时自己电脑装Nginx过程略微麻烦但讲清楚了是加分项。第二种更简单直接把dist目录下的文件复制到SpringBoot项目的src/main/resources/static目录重新打包成Jar包。启动Jar包后浏览器直接访问http://localhost:8080前后端同源没有任何跨域烦恼。这种方式演示最稳建议联调阶段用Nginx或前端代理最终交付时用合并部署减少很多现场演示的突发情况。6. 论文写作与答辩准备的实操建议论文质量直接决定毕设成绩的上限。代码写得再好论文一塌糊涂也是白搭。选题系统相关的论文结构比较固定但很多细节值得打磨。6.1 论文的核心章节与图表清单一篇合格的毕设论文至少包含以下章节绪论、需求分析、系统设计、系统实现、系统测试、总结与展望。系统设计章节要画一个系统架构图说明前后端分离结构数据库章节必须有E-R图和数据表设计说明。流程相关的内容用活动图或者时序图很容易讲清楚比如选课的超时冲突逻辑、管理员审核课程的状态流转等。画图工具用ProcessOn或draw.io操作上手快且数据在云端不怕丢。系统架构图不要画得太复杂从上到下四层就够表现层Vue、接入层SpringBoot Controller、业务层Service、数据层MySQL。层与层之间用箭头标出交互老师看架构图基本就能了解你的技术栈组成。6.2 论文里代码贴法与查重处理论文里的核心代码要选关键片段不要整段贴上去。选课的事务控制逻辑、JWT认证拦截器、乐观锁更新SQL是三大关键代码片断建议用伪代码加少量真实代码结合的形式呈现。最终论文查重时大段完整代码字数算入重复率尽量减少代码块篇幅对降重有明显帮助。查重问题有两个特别实用的经验一是数据库表设计说明和需求分析部分尽量用自己的话重新组织不要对着模板改几个词就完事知网的重复率检测对连续13个字相同就能检出二是毕设系统里涉及的业务逻辑描述用具体的字段名和操作流程写出来而不是用“系统可以方便快捷地实现”这种万金油句。6.3 答辩演示的准备经验答辩红牌警告有两个一个是演示时崩溃另一个是老师问实现细节答不上来。演示要提前准备好一套完整演示数据包括一个管理员账号、一个教师账号、三个学生账号以及预置好的课程数据。演示流程按照业务闭环走管理员登录维护课程 - 教师登录录入成绩 - 学生选课退课 - 学生查询成绩 - 教师查看选课名单 - 管理员查看统计。闭环演示比零散点几个页面有说服力得多。准备答辩时把核心代码的逻辑链路理清楚前端点击选课按钮后请求经过哪些层数据库做了什么操作哪个环节会校验课程容量哪个环节会判断时间冲突。准备两个展示亮点“并发场景下如何防止超选”和“JWT无状态认证的原理”。这两个问题讲清楚基本能应对80%的追问场景。7. 常见问题与排查技巧实录这些坑是我身边学生实际操作中真实遇到过的典型问题列成清单方便对照排查。后端相关的居多因为后端的问题不太容易一眼看出原因。7.1 前端请求接口报跨域错误现象浏览器控制台出现Access to XMLHttpRequest at ... has been blocked by CORS policy。排查思路先确认请求有没有真的到达后端。打开POSTMAN直接访问后端接口能通说明后端没问题。然后确认是否因为JWT拦截器拦截了OPTIONS预检请求加上对OPTIONS请求的放行逻辑。再确认后端的CORS配置是否生效如果配置了Spring Security还需要单独配置Security层面的CORS规则。7.2 数据库连接超时或Access denied现象项目启动日志里报Communications link failure或Access denied for user。排查思路数据库连接串里的serverTimezone是否配置正确MySQL服务有没有启动起来Windows上可以在服务管理里确认用户名和密码是否与application.yml里一致。最隐蔽的一种情况是MySQL 8.0以上版本的驱动必须显式配置useSSLfalse否则会有个SSL警告一般不影响运行但不加显得不规范。7.3 Maven依赖下载慢或下载失败现象pom.xml里的依赖一直报红或者构建日志卡在原地不动。排查思路Maven默认中央仓库在国内访问较慢在settings.xml中配置阿里云镜像源mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror依赖报红后右侧的Maven面板点刷新按钮强制重新加载项目配置。修改完pom.xml没有生效也是这个原因刷新几次就能解决。7.4 数据更新成功但前端没有变化现象明明选课成功了课程列表的已选人数却没有变化。排查思路前端列表数据是否在操作后重新请求了后端接口。如果刷新页面后有变化说明是前端没有重新拉数据。最好在选课成功回调里重新调一下课程列表接口或者用全局状态管理如Pinia存选课状态统一控制刷新时机。7.5 Element Plus表格表格翻页后按钮状态丢失现象第一页选了一门课后翻到第二页再翻回来第一页的按钮又恢复成“选课”状态。排查思路课程列表纯靠前端本地变量控制状态翻页后数据重新渲染就会丢失。正确做法是已选课程的判断依据一律以后端返回的字段为准课程列表接口返回当前学生是否已选该课程前端根据这个字段渲染按钮这样无论怎么翻页状态都不会丢。注意做毕设的核心不是“照搬一套代码”而是理解为什么这样设计。答辩时最怕的就是项目能跑但解释不了设计决策。每个模块能讲清楚“为什么选这个方案”和“这个方案有什么坑”老师对你的评价自然会上去。8. 后续可以怎么扩展这个系统如果做完基础功能后还有余力以下几个方向可以让整个项目更有深度。第一是引入Redis缓存课程列表和选课状态减少数据库压力这可以在设计文档里写清楚缓存策略以及缓存和数据库的一致性处理第二是给选课增加时间批次限制按年级分批开放选课时间这需要用一个定时任务模块能体现项目真实使用场景中的复杂性第三是在线支付与收费课程的结合点虽然系统本身不需要对接支付但“选修课收费”这个业务逻辑可以丰富整个项目的业务范围。功能扩展不用贪多选一个方向做深即可。做得深入能在“总结与展望”章节里写下自己的真实思考答辩时也能体现出独立解决问题的能力。最后再分享一条个人经验很多人在网上找毕设源码这对自己的项目肯定有帮助但直接下载提交的占多数。建议在此基础上把核心模块至少自己重写一遍比如把Controller改成自己喜欢的接口风格或者把数据库表结构按自己的理解重新设计一次。自己动手改过的代码哪怕只是改了一点点在答辩时被问到细节也不至于全忘干净。祝所有在做选课系统的同学都能顺利过关。