尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot+Vue大学生选课系统:前后端分离与并发控制实践
如果你正在为Java Web方向的毕业设计发愁这套SpringBootVue构建的大学生选修选课系统可能是最容易落地、也最容易被评委认可的题目之一。它覆盖了前后端分离、权限控制、数据库设计、并发处理、接口文档编写等完整环节既能体现扎实的后端功底又能展示前端交互能力。我当时基于这套完整源码、SQL脚本和接口文档把系统从0到1跑通又给某高校做了一个简化版用了差不多半个月。核心代码量不大但踩的坑不少今天把这套系统的设计思路、实现细节和避坑经验整理出来希望能帮到正在选这个题目的同学。1. 项目整体设计与技术选型1.1 系统核心需求拆解大学生选修选课系统的业务其实很典型学生登录后能查看可选课程、选课、退课、查看已选课表教师能发布课程、查看选课名单管理员能管理用户、课程、统计选课数据。听起来简单但真正做起来要处理的需求细节非常多。我一般会先画角色用例图把三类角色学生、教师、管理员的权限边界划清楚。学生端最常见的操作是“选课”和“退课”选课时要判断课程容量是否已满、是否与已有课程时间冲突、是否重复选课教师端要能对课程进行增删改查课程信息包括课程名、教师、上课时间、上课地点、学分、容量等管理员端负责账号管理、课程审核如果需要、以及导出选课名单之类的功能。把这些需求全部列出来后再去设计表结构就不会漏字段。这里有一个细节容易被忽略选修课往往还有“预选”和“正选”两轮甚至有的高校需要学生先抽签再确认。为了压缩工作量我把系统锁定为“即选即得”模式也就是选课时只要容量没满、时间不冲突立即成功。如果要做抽签复杂度会明显上升但这不影响主流程展示。建议你也先做通基础流程再考虑扩展。1.2 为什么选SpringBootVue先说后端。SpringBoot是目前Java Web项目最主流的框架它内置了Tomcat简化了Spring的配置只要引入相关依赖就能快速启动项目。配合MyBatis-Plus做ORM再加上Sa-Token做权限控制整个后端可以非常快速地搭建起来。选课系统这类CRUD密集型项目SpringBoot的生态和资料丰富度都是第一梯队遇到问题几乎都能搜到解决方案。前端Vue的话现在主流是Vue 3 Element Plus配合Vite构建工具页面开发效率高。Vue的双向数据绑定非常适合表单类操作选课这种需要实时反馈的场景用Vue做用户交互非常顺手。前后端通过RESTful API通信返回JSON数据结构清晰。这套组合在求职面试中也很有竞争力因为企业里真的在用。我选择使用Sa-Token而不是Spring Security主要原因就是Sa-Token简单得多几行代码就能完成登录和权限校验对于毕设项目来说足够用而且文档是中文的上手成本低。如果你用Spring Security光配置类就要写一堆很多同学卡在那里一个月。这不是说Spring Security不好而是选课系统这类项目用Sa-Token更务实。有人可能会问前端用JQuery或Thymeleaf不也行吗当然行但那就体现不出“前后端分离”的现代工程能力了。毕设答辩时评委对前后端分离的认可度普遍很高而且Vue相关技能在就业市场更受欢迎。这就是我为什么坚持这个组合。2. 数据库设计与核心表结构数据库是选课系统的地基。很多人喜欢一上来就写代码结果后面发现字段不够用或者关系理不清反复改表。我建议先花半天把表设计好后面写代码会顺畅很多。2.1 核心表梳理我设计的核心表有六张用户表sys_user、角色表sys_role、用户角色关联表sys_user_role、课程表course、选课记录表course_selection、课程时间安排表course_schedule。如果课程有多个时间段可能还需要独立的时间表但为了简单很多课程都是一周一次可以放在课程表里也可以单独建表。我选择单独建表因为一个课程可能分为多个班次或者多个时间段。以用户表为例字段大致有id、username、password、nickname、status、create_time等。密码一定要加密存储我用的BCrypt。课程表字段包括id、course_name、teacher_id、credit、capacity、selected_count、semester、position等。选课记录表包括id、student_id、course_id、select_time、status正常/退课等。下面用一个表格列出主要表结构的关键字段方便对照表名关键字段说明sys_userid, username, password, nickname, status用户主表存学生、教师、管理员sys_roleid, role_name角色表如ROLE_STUDENT、ROLE_TEACHER、ROLE_ADMINsys_user_roleid, user_id, role_id用户与角色多对多关联courseid, course_name, teacher_id, credit, capacity, selected_count, semester课程主表包含课程基本信息course_scheduleid, course_id, day_of_week, start_time, end_time, position课程时间地点表course_selectionid, student_id, course_id, select_time, status选课记录表注意实际开发中角色关系也可以用更简单的方式比如直接在用户表里加一个role_id字段。但如果一个用户可能有多个角色那还是单独建关联表更规范。毕设中我建议用关联表因为评委会认为你考虑了扩展性。2.2 选课冲突与容量控制的逻辑选课系统的核心业务是“不能超员”和“不能时间冲突”。关于容量控制我在course表里设计了capacity和selected_count两个字段每次选课时先判断selected_count capacity再执行更新。但是这里有个经典问题如果两个请求同时读到同一剩余名额然后同时插入就可能超员。解决办法有两个一个是给课程记录加乐观锁版本号另一个是使用数据库行锁比如在更新时使用UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity这样数据库会锁住行保证并发安全。我采用的是第二种最简单也最可靠SQL里直接判断剩余容量。时间冲突的判断放在应用层就可以因为学生的选课请求频率不会特别高应用层查询已选课程的时间段再对比新课程的时间段即可。如果做到真正的万人选课高并发才需要考虑缓存和分布式锁但毕设不需要那么极端。我在实现时把冲突判断写在事务里先查学生的已选课程列表再判断有无时间重叠最后插入选课记录并更新课程容量。整个方法加上Transactional注解如果有任何异常就回滚保证数据一致性。这里还要注意一个细节退课的时候必须把selected_count减回去否则课程容量会越来越少。我遇到不少同学只做了插入记录忘记了更新计数导致课程明明没人选却显示已满。这个错误很隐蔽自查时一定要走一遍“选课→退课→再选课”的完整流程。3. 后端接口设计与实现细节后端部分我是按照标准的Controller-Service-Mapper三层结构来写的。每一个接口都做参数校验和异常处理返回统一的Result对象。Result对象里至少包含code、message和data三个字段这样前端拿到数据后可以统一判断。3.1 登录鉴权与RBAC权限模型系统的访问控制采用RBAC模型就是用户-角色-权限三层。在Sa-Token中我注册了登录token登录成功后把用户id和角色存到StpUtil的session中然后通过SaCheckRole(student)等注解控制接口访问。比如选课接口需要学生角色发布课程接口需要教师角色管理用户接口需要管理员角色。这样非常简洁。登录接口的流程是前端提交用户名密码后端查库并校验BCrypt密码通过后调用StpUtil.login(userId)返回token给前端。前端把这个token放到请求头中通常是Authorization: Bearer xxx后端通过拦截器自动解析。注意密码不能明文传最好前端用HTTPS或者至少在后端进行传输保护。我写接口文档时会特别说明token的传递方式。实际开发中我把所有需要登录的接口都放在一个以/api开头的路径下然后在Sa-Token拦截器中配置排除路径列表比如/login、/captcha等不需要登录的接口可以直接放行。这样权限控制逻辑很清晰。后端每一个接口都要校验角色不能只靠前端隐藏按钮来限制否则别人直接调用接口就能绕过权限。3.2 选课接口的并发处理选课接口是学生端的核心接口。路径一般设计为POST /api/student/selectCourse请求参数包括courseId和studentId也可以从token中获取。我当时的实现是Transactional public Result selectCourse(Long courseId, Long studentId) { // 1. 检查课程是否存在 Course course courseMapper.selectById(courseId); if (course null) return Result.error(400, 课程不存在); // 2. 检查重复选课 CourseSelection exitSelection selectionMapper.selectOne( new LambdaQueryWrapperCourseSelection() .eq(CourseSelection::getCourseId, courseId) .eq(CourseSelection::getStudentId, studentId) .eq(CourseSelection::getStatus, 1)); if (exitSelection ! null) return Result.error(400, 不能重复选课); // 3. 检查时间冲突略 // 4. 原子更新容量selected_count capacity 才更新 int updated courseMapper.reduceStock(courseId); if (updated 0) return Result.error(400, 选课人数已满); // 5. 插入选课记录 CourseSelection selection new CourseSelection(); selection.setCourseId(courseId); selection.setStudentId(studentId); selection.setStatus(1); selection.setSelectTime(new Date()); selectionMapper.insert(selection); return Result.success(选课成功); }其中reduceStock的SQL大概是这样update idreduceStock UPDATE course SET selected_count selected_count 1 WHERE id #{courseId} AND selected_count capacity /update这个写法的好处是数据库在update时会对该行加锁多个请求同时选同一门课时只有一个能更新成功其他请求的updated为0也就不会出现超选。这个思路我在项目里反复强调因为这是选课系统最容易出问题的地方。另外如果选了课以后又退课要记得回滚selected_count。有同学会想为什么不用Redis分布式锁因为对于单体应用来说数据库行锁已经足够不需要引入额外组件。你再加一个Redis反而会增加部署复杂度。把精力放在业务正确性上比什么都重要。3.3 接口文档的编写很多人看不起接口文档觉得写代码就够了。但毕设答辩时你一本接口文档摆出来评委对你的印象会好很多。我用的是Swagger注解方式配合Knife4j能自动生成在线接口文档。不过我也自己整理了一份Markdown格式的接口说明表把每个接口的URL、请求方式、参数、返回结构写清楚。这是非常好的加分项。我给大家看一下我自己接口文档中的某一段格式接口名称请求方式路径参数说明学生选课POST/api/student/selectCoursecourseId, studentId选课成功返回code200学生退课POST/api/student/cancelCoursecourseId, studentId退课后释放容量课程列表GET/api/course/listpage, size, semester分页查询课程我的课表GET/api/student/myCourses无查询已选课程列表发布课程POST/api/teacher/coursecourseName, credit, capacity...教师创建课程用户管理GET/api/admin/userspage, size管理员分页查用户接口文档一定要和实际代码对应不要写完之后就忘了更新否则联调时前端会经常找你说“接口怎么调不通”其实大多数是参数名不一致或者返回结构变了。我有个习惯每写完一个接口立刻更新文档和Postman示例确保别人拿到项目后可以快速调用。这套项目里的接口文档我按模块整理成了md文件放在docs目录下和源码一起交付。4. 前端页面与Vue组件设计前端部分我没有用特别复杂的方案就是Vue3 Vite Element Plus Pinia Axios。页面不多但胜在结构清晰。很多人可能觉得前端难但用上Element Plus之后大部分后台页面都是表格、表单、弹窗的组合复制组件改一改就行真正的工作量主要在路由和状态管理上。4.1 页面结构与路由权限我整理出来的页面清单大致是登录页、学生首页课程浏览、选课、我的课表、教师首页课程管理、选课名单查看、管理员页面用户管理、课程审核、数据统计。因为角色不同前端用动态路由方式登录后根据角色加载不同的路由表。下面是一个简化的前端路由设计路径组件权限/loginLogin.vue无/student/coursesStudentCourses.vue学生/student/my-coursesMyCourses.vue学生/teacher/coursesTeacherCourses.vue教师/teacher/studentsTeacherStudents.vue教师/admin/usersAdminUsers.vue管理员这里要特别说一下Vue Router的守卫。我使用前置守卫检查是否有token并调用后端 /api/user/info 获取当前用户角色然后动态添加路由。如果角色不匹配直接跳转到403页面。这样权限在前后端都做了控制更安全。很多同学的动态路由写法是在main.js里把所有路由一次性注册包括admin、teacher、student的页面这样虽然能跳转但普通学生也能通过改URL访问到管理员的页面只是页面请求接口时会被后端拦截体验很差。正确做法是只有登录后前端根据角色把对应模块的路由表添加进路由实例。这也不算难但能体现你对权限的理解。4.2 状态管理与Axios封装状态管理用的是PiniaVue 3官方推荐。我建了一个store里面保存用户信息、token和角色。登录成功后将token存到localStorage刷新时从localStorage恢复到store中。Axios封装时加了一个请求拦截器每次请求自动带上token响应拦截器统一处理错误状态码比如401跳转登录页403跳转403页。这样前端的代码量大幅度减少。Axios封装的核心逻辑大致是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; }, error Promise.reject(error)); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { return Promise.reject(new Error(res.message || 请求失败)); } return res; }, error { if (error.response error.response.status 401) { router.push(/login); } return Promise.reject(error); } );所有api请求单独放在api目录下每个模块一个文件比如student.js里面就是选课、退课相关的请求函数。这样维护起来非常方便。我习惯给每个接口函数写上注释比如“通过课程id选课”这样过一段时间再看代码也能快速回忆起用途。4.3 与后端对接的注意事项我遇到过的第一个坑是跨域问题。开发时前端在localhost:5173后端在localhost:8080肯定跨域。解决方式是在后端配置CorsFilter或者加CrossOrigin注解。但生产环境最好是统一用Nginx反向代理把 /api 路径转发到后端这样不存在跨域。我在部署时就是这样的。第二个容易忽略的是时间字段的序列化问题。Java后端返回的时间格式是yyyy-MM-dd HH:mm:ss默认情况下Spring会转为时间戳前端拿到的是数字显示起来很别扭。在jackson配置里设置日期格式即可spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这样前后端时间格式统一就不容易出bug。第三个坑是前端的表单校验。选课的时候如果用户连续点击两次按钮可能导致发出两个选课请求而后端又没有做幂等处理结果就会产生两条选课记录。我在前端给选课按钮加上loading状态在点击后立即禁用按钮然后等接口返回再恢复。这个细节虽然简单但很能体现工程素养。5. 部署、常见问题与避坑实录代码写完只是第一步部署上线时往往能暴露出一堆问题。我用的是前后端分离部署方式前端打包成dist后用Nginx托管后端打成jar包用java -jar运行。5.1 打包部署与Nginx配置后端打包命令是 mvn clean package -DskipTests生成target目录下的xxx.jar。运行命令是 java -jar xxx.jar --spring.profiles.activeprod。我习惯将数据库配置放到application-prod.yml中分开配置开发环境和生产环境。前端打包是 npm run build生成dist目录然后把dist里的文件放到Nginx的html目录下。Nginx反向代理配置核心是location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }注意proxy_pass后面的路径是否带斜杠这会导致路径拼接问题我踩过坑。比如请求 /api/course/list如果proxy_pass http://127.0.0.1:8080; 则会转发到 /api/course/list如果写成 http://127.0.0.1:8080/; 则会把 /api 前缀去掉转发到 /course/list后端没有这个路径就会404。很多人就是因为这个细节调了半天。另外前端路由如果用history模式Nginx还需要配置try_files否则页面刷新时会出现404。这部分配置是这样的location / { try_files $uri $uri/ /index.html; }没有这个配置用户按F5刷新页面就会白屏这个也是毕设演示时的高频翻车点。5.2 常见问题排查表下面把我在开发和测试中遇到的典型问题列一个表方便你自查症状可能原因解决方案登录后接口401token没有传或过期检查Axios请求拦截器确保Authorization头正确课程列表为空数据库没有数据或SQL查询条件错误检查application.yml数据源配置和SQL日志选课提示人数已满但实际没满课程容量字段没有初始化或selected_count没有正确更新确认course表容量数据手动更新或调用管理端接口修正前端点击选课没有反应跨域问题或接口地址错误看浏览器控制台确认请求是否发出以及响应状态前后端时间相差8小时时区配置不对在jdbc url加serverTimezoneAsia/Shanghai并设置jackson时区管理员无法删除用户有外键约束用户已关联选课记录先逻辑删除或先删除关联记录这个表是我实际排查过的虽然具体原因可能略有不同但大致方向就是这个。排查问题时先打开浏览器的Network面板看请求发出去了没有响应是什么状态码再根据状态码去后端日志里找错误。这一套流程走下来90%的问题都能定位。5.3 经验总结与扩展方向做这种完整的毕设项目我有几点特别想分享。第一不要为了炫技引入花里胡哨的技术比如把后端拆成微服务或者用Redis做缓存。选课系统是单体应用足够解决的事情你拆微服务反而会被评委追问“你怎么解决分布式事务”自己挖坑自己跳。第二SQL脚本一定要准备一份。我提供的SQL脚本里包含了建库、建表和初始数据管理员账号、老师账号、学生账号等这对接手的人来说非常方便。很多同学交代码不给SQL脚本老师想运行都运行不起来。第三代码注释和接口文档要同步维护。我给代码写注释的习惯是“注释是解释为什么而不是复述在做什么”。如果你还想扩展功能可以考虑这几个方向选课时间段批次比如第一轮预选、第二轮正选、学生绩点筛选、课程评价、选课数据可视化统计。这些都是基于现有表结构相对容易扩展的也能给答辩增加亮点。我在实际运行中发现用这个系统处理几百个学生的选课压力毫无问题查询响应都在毫秒级别。如果你需要真正的高并发再考虑引入Redis缓存课程列表给选课加分布式锁但那是后话。对毕业设计和中小型校内系统来说现在这个方案已经足够了。希望这套SpringBootVue的源码项目能帮你顺利交差也能让你在答辩时对每个模块的解释都心里有底。最后再分享一个小技巧第一次运行时建议先跑SQL脚本然后直接用管理员账号登录先用管理员建教师账号、用教师账号建课程再用学生账号选课走通全流程。千万别自己不先跑一遍答辩时现场连数据库都连不上。我把这一步称作“地毯式流程自测”至少可以提前发现初始化数据、外键约束和权限配置三方面的低级问题。祝你的毕设一次通过。
RELATED

相关推荐

药品商城推荐系统实战:基于协同过滤的Java+Vue全栈实现

药品商城推荐系统实战:基于协同过滤的Java+Vue全栈实现

做药品商城不难,难的是让用户在你这个商城里找到真正需要的东西。这句话是我带过好几个做电商类项目的开发者之后,感触最深的一点。常规的商品分类、搜索、筛选做完,用户还是靠瞎逛,复购率上不去。所以当某高校项目组把“基于协同…

📅 2026/10/22 2:56:49
MCP协议实战:从零开发第一个MCP Server,让Agent调用你的服务

MCP协议实战:从零开发第一个MCP Server,让Agent调用你的服务

最近连续接了几个项目,开场白几乎一模一样:能不能让 AI 帮我们操作一下内部系统?需求一听就懂,一做就卡住,卡就卡在模型根本没有一条标准的路去调用我那一堆内部服务。早期各家都在做自己的工具调用格式,接…

📅 2026/10/22 2:51:49
语言模型与复杂网络结合的文化传播预测技术链路

语言模型与复杂网络结合的文化传播预测技术链路

从标题看,这是一个典型的交叉学科落地型项目:语言模型负责从海量非结构化文本里提取“人、事、情”的语义信号,复杂社会网络则负责承载这些信号在关系链上的传导与演化,最终目标是让“文化传播”这种看似虚无缥缈又极难量化的东西…

📅 2026/10/22 2:51:49
MORE NEWS

更多资讯

📰

HBase建表源码深度解析:从客户端到Master的完整链路与Procedure状态机

1. 从一次建表超时说起:为什么要啃 HBase 建表源码如果你在生产环境维护过 HBase 集群,大概率遇到过这样的场景:客户端调用createTable()之后卡住不动,或者返回超时异常,但表实际上已经建好了;又或者建表请…

📰

C++ Qt 学生管理系统实战:从 MySQL 建库到增删改查完整工程

简介:基于C/Qt与MySQL构建的学生信息管理系统完整工程,面向计算机相关专业毕业设计、课程设计场景,适合需要一套可直接运行的桌面端数据库应用参考的开发者。工程共79个文件,核心由26个C源文件、25个头文件和23个Qt界面文件组成&a…

📰

旅游数据清洗与可视化实战:从字段体检到五大分析切面

1. 拿到一份"三无"旅游数据时,我为什么先做字段体检而不是直接画图很多人拿到一份旅游景点数据,第一反应是打开绘图库,先画个柱状图看看哪个城市景点多。我早年也这么干过,结果图是画出来了,汇报的时候被问了…

📰

LoRA微调全指南:低秩分解原理与消费级显卡实操方法

LoRA微调大概是目前把一个大模型变成“你的模型”最划算的一条路。这篇文章直接用实操视角讲清楚 LoRA 是什么、为什么它比全量微调更适合个人和小团队,以及从数据准备到训练脚本到排查问题的一整套流程。适合那些已经跑通过模型推理、但还没系统做过微调的朋友&…

📰

SSM知识产权管理系统:框架原理、部署避坑与工程实践

简介:面向Java Web初学者的知识产权管理系统完整源码包,采用SSM(SpringSpringMVCMyBatis)框架,结合JSP技术、MySQL数据库与Eclipse工具开发,前后台一体化设计。系统包含管理员、员工、注册用户三类角色&…

📰

基于YOLOv8与PyQt的智能监考系统设计与实现:目标检测毕设全流程解析

1. 这套组合为什么值得做成毕设:YOLOv8 PyQt的业务逻辑1.1 监考场景对检测算法的要求现在做计算机毕设,目标检测方向的热度一直很高,但很多同学容易陷入一个误区:上来就跑COCO预训练模型,detect个猫猫狗狗&#xff0c…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬