ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Spring Boot的第二课堂选课系统开发实战:从并发控制到学分认定

基于Spring Boot的第二课堂选课系统开发实战:从并发控制到学分认定 每年到了毕业设计选题季总有一批人盯着第二课堂这三个字反复纠结。作为过来人我很清楚这个题目为什么能一直火下去它本质上是个带管理后台的选课平台既要处理学生端的高并发选课又要搞定教师端的活动发布还得把学分认定这种偏行政化的流程塞进系统里技术点上覆盖了权限管理、定时任务、分布式锁、事务控制论文素材一抓一大把演示效果也直观。我自己就帮几个学弟梳理过基于 Java 和 Spring Boot 的第二课堂选课系统从功能设计到代码落地踩了不少坑今天就一次性把这些经验整理出来。这个系统解决的是高校第二课堂活动中选课靠抢、记录靠纸、学分靠人工的痛点核心是三个字选、管、认。选指学生在线选报素质拓展课程管指教师和教务对活动进行发布、审核、名额管理认指系统根据参与记录自动累计学时并生成学分认定。无论你是要做毕设、课程设计还是单纯想练手 Spring Boot 全栈开发这篇内容都值得参考。我尽量把每个技术决策背后的原因也讲清楚——只讲怎么做不讲为什么到了答辩现场很容易被问住。1. 项目整体设计思路先别急着写代码把三个软件需求理清楚1.1 三个标题背后的统一核心需求你拿到的题目可能有三种表述方式——基于 Java 的第二课堂选课系统基于 Spring Boot 的课外素质拓展课程在线选报平台第二课堂活动管理与学分认定系统。看起来名字不一样其实说的是同一个系统。把它拆开看本质就是三个核心业务闭环活动发布闭环、学生选课闭环、学分认定闭环。立项时不需要纠结名字你需要做的是把学校第二课堂管理这个场景抽象成一个可落地的软件需求模型。先说活动发布闭环。学校里有各种各样课外活动文体竞赛、志愿服务、学术讲座、社团实践等等。每类活动有不同的组织方、不同的场地、不同的名额和时间。系统要有角色让老师能创建活动填写名称、类别、地点、时间、容量、学分值这些基础字段提交后由管理员审批审批通过的课程才会进入学生端可报名列表。这个流程对应后台管理中的活动管理和审核管理。然后是学生选课闭环。学生登录后能够浏览课程列表按类别筛选、按时间排序看到心仪的课程点击选课。这里要处理的核心矛盾是名额有限选课人太多。因此系统需要保证同一课程学生不能重复选课程名额不能超过容量同一时间段的课程不能冲突这三个约束直接用数据库唯一索引和业务逻辑双重校验。选课成功后生成一条选课记录后续的出勤签到、活动评价都挂在这条记录下面。最后是学分认定闭环。学校对于第二课堂学分往往有标准比如每学期必须有 X 个学时的志愿服务、X 次文体活动。系统按照类别统计学生累计学时当达到标准后自动生成学分认定申请或由辅导员审批后完成认定。这个模块虽然不复杂但它是整个系统区别于普通选课系统的关键——也是论文里创新点最好写的地方。1.2 角色权限设计四个角色对应四种入口系统角色我建议设计为四种学生、教师、辅导员、系统管理员。你没有看错是四种而不是三种。很多同学一开始只设计了学生、教师、管理员三个角色做到学分认定的时候才发现如果只有管理员能操作认定辅导员的工作全压到管理员身上流程上不合理。把辅导员拆出来权限层级清晰答辩时讲权限管理也能多一个维度。学生端功能集中在选课和查看记录浏览课程、在线选课、退选、查看已选课程、查看学时和学分认定进度。教师端功能集中在活动全流程发布活动、管理自己课程的学生名单、进行签到考勤、录入活动成绩。辅导员端功能集中审阅统计查看所带班级学生的参与情况、审批学分认定申请。系统管理员负责基础数据维护和全局管理用户管理、课程分类管理、活动审核、认定规则设置、数据统计。1.3 功能模块拆分先分模块再做表思路就不乱了实操中建议按前台展示—核心业务—后台管理三层拆功能。前台对应首页、课程列表、个人信息页、学分页核心业务对应选课、退选、签到、认定申请后台管理对应活动管理、用户管理、审核流、规则配置。这样拆的好处是写代码的时候 Controller 层的命名能保持高度统一Service 层互相调用的边界也清晰不会出现一个 Service 类长到几百行的情况。功能模块划分直接决定数据库表结构。我见过不少同学数据库表建了十几张字段堆得密密麻麻结果代码里还是没法查清楚某个学生的学时到底怎么算出来的。根源就是模块边界没想清楚。如果你是第一次做这种系统建议先画一张角色-功能矩阵图横轴是角色纵轴是操作然后对照矩阵建表、划分接口后面写起来会顺不少。2. 技术选型与架构搭建Spring Boot 四层架构怎么落地2.1 为什么选 Spring Boot 而不是 SSM毕业设计要的是平稳落地搜索热词里一直有人问 spring boot 四层架构是什么、spring boot 目录规范怎么做这恰好是毕设选型阶段最容易纠结的地方。横向对比三种常见方案纯 Servlet JSP 已经过时写起来代码量大SSMSpring SpringMVC MyBatis结构经典但是配置繁琐光 XML 配置就能劝退一半人Spring Boot 自动配置 内嵌 Tomcat Starter 生态是当下最稳的选择。Spring Boot 的四层架构具体指的是 Controller 层、Service 层、MapperDao层、EntityModel层。Controller 层负责接收请求、参数校验、返回结果Service 层写业务逻辑——注意是核心业务逻辑都在这层Mapper 层只做数据库交互Entity 层映射数据库表。我实测下来这个分层最大的价值是调试效率高前端传参出问题查 Controller数据算错查 ServiceSQL 有问题查 Mapper定位基本一次到位。提供一个目录参考这也是我常用的规范结构com.example.secondclass ├── controller // 接口入口 ├── service // 业务逻辑接口 │ └── impl // 业务逻辑实现 ├── mapper // mybatis mapper接口 ├── entity // 实体类 ├── dto // 数据传输对象 ├── vo // 视图返回对象 ├── config // 配置类跨域、拦截器等 ├── utils // 工具类 └── common // 通用返回结果、异常处理2.2 数据库设计五张核心表和两个关键索引数据库设计是整个项目的重心也往往是答辩评委第一个下手问的地方。建议从最简方案入手先画五张核心表再根据功能点扩展关联表。这五张表是用户表user、活动课程表activity、选课记录表selection、学分认定表credit_apply、活动类别表category。用户表字段至少包含 id、username、password、real_name、role、student_no、class_name。role 字段用字符串STUDENT、TEACHER、COUNSELOR、ADMIN区分角色比拆五张用户表省事得多。密码存储必须做加密处理用 BCrypt 而不是 MD5这一点写论文时要主动提。活动课程表字段包含 id、title、category_id、teacher_id、location、start_time、end_time、capacity、selected_count、credit_hours、status、description。这里的 capacity 和 selected_count 是一个经典的库存模型选课时要同时判断 count 是否小于 capacity。选课记录表是整个系统的核心字段为 id、student_id、activity_id、status、create_time。给 student_id 和 activity_id 建联合唯一索引这是防止学生重复选课的最底层防线。实操中很多同学只在前端做判断这一个索引就能直接堵住并发情况下的重复提交漏洞。学分认定表字段为 id、student_id、total_hours、category_hours、semester、status、audit_teacher、audit_time。活动类别表字段为 id、name、credit_standard、sort_order。2.3 认证授权方案JWT 还是 Session选哪个好落地认证授权是答辩必问的一个点。这个系统我用的是 JWT 无状态方案原因是前端展示页和后端接口可以完全分离而且登录拦截逻辑写起来清晰。JWT 的原理简单说就是把用户 id、角色、过期时间这些信息做数字签名放进一个 token 里服务端不需要存 session只需要验签。缺点是 token 无法主动吊销但对于毕设项目来说完全可以接受。实际落地时用 Spring Boot 拦截器实现登录校验写一个 JwtInterceptor 类实现 HandlerInterceptor 接口在 preHandle 方法里从请求头取 token解析成功放行失败返回 401。然后在 WebMvcConfigurer 里注册拦截器通过 addPathPatterns 和 excludePathPatterns 配置哪些接口需要登录、哪些接口放行。这一步看起来平淡无奇但我在帮人调试时发现很多同学把拦截器配完之后连登录接口都进不去了原因是 excludePathPatterns 没把 /api/login 排除掉。3. 核心模块实现选课、学分认定、定时任务一个都不能少3.1 选课报名与退选逻辑事务、锁、唯一索引三重保障选课是这台系统的车头也是并发压力最大的地方。我把选课逻辑简化成一个 createSelection 方法流程如下先校验活动状态是否在选课中再校验学生是否已经选过查表再校验名额是否已满selected_count capacity然后再校验时间是否冲突查询该学生已选课程是否与当前课程时间重叠全部通过后插入选课记录并 update activity 表的 selected_count 加一。这段逻辑表面看没问题但一旦两个学生同时抢最后一个名额就会出现超选。所以要加事务和锁。我会在 Service 方法上加 Transactional 注解同时用悲观锁解决数据一致性问题——在查询活动记录时加SELECT ... FOR UPDATE把这一行的写锁拿到手再执行校验和更新。这样同一时刻只有一个用户能操作该活动的选课流程另一个用户会阻塞等待。虽然牺牲了一点并发性能但名额只有几十个完全够用。核心的校验代码大概长这样Transactional public Result createSelection(Long studentId, Long activityId) { // 1. 悲观锁查询活动 Activity activity activityMapper.selectByIdForUpdate(activityId); if (activity null || !OPEN.equals(activity.getStatus())) { return Result.error(活动不存在或不在选课期); } // 2. 查重数据库唯一索引兜底 if (selectionMapper.countByStudentAndActivity(studentId, activityId) 0) { return Result.error(请勿重复选课); } // 3. 判断名额 if (activity.getSelectedCount() activity.getCapacity()) { return Result.error(课程名额已满); } // 4. 时间冲突检测 if (selectionMapper.countTimeConflict(studentId, activity.getStartTime(), activity.getEndTime()) 0) { return Result.error(所选课程时间冲突); } // 5. 插入记录 更新名额 selectionMapper.insert(new Selection(studentId, activityId)); activityMapper.increaseSelectedCount(activityId); return Result.success(); }退选逻辑相对简单但要注意一个隐藏坑退选时如果只删选课记录忘记把 selected_count 减回来后面名额会越来越少甚至变成负数。我在实际测试时见过有同学把名额刷成负数原因就是 delete 之后没做 update。退选操作中只要满足活动未开始且未签到即可同样加上事务确保数据一致性。3.2 学分认定与学时统计别把聚合逻辑写死在查询里学分认定要解决的核心问题是学生参加的活动怎么折算成学分一条一条记录怎么按类别汇总。很多同学第一反应是在 Java 代码里把记录查出来再用循环累加学时。这个方案在数据量小的时候没问题但答辩评委一句数据量上万之后这个接口会多慢就能把你问住。合理的做法是用 SQL 做聚合查询让数据库去算代码只负责接收结果。比如统计学生每学期各分类学时一条 SQL 就行SELECT a.category_id, c.name AS category_name, SUM(a.credit_hours) AS total_hours, COUNT(*) AS activity_count FROM selection s JOIN activity a ON s.activity_id a.id JOIN category c ON a.category_id c.id WHERE s.student_id #{studentId} AND s.status FINISHED AND s.semester #{semester} GROUP BY a.category_id, c.name拿到这个结果汇总后和认定规则配置对比。判断逻辑可以放在 Service 层也可以用规则表动态配置比如规定志愿服务一学期至少 10 学时满足条件后学生可以发起认定申请状态变为 PENDING辅导员审核后变为 APPROVED。关于自动认定还是学生申请后认定我建议做成学生申请辅导员审批的半自动模式既体现流程管理又给系统增加了一个角色交互的场景答辩时好讲。3.3 活动定时任务用 Scheduled 实现自动上下架和批量通知活动状态流转是容易被忽略但很体现细节的地方。一场活动的状态我设计了四档待审核PENDING、报名中OPEN、进行中ONGOING、已结束FINISHED。状态变化一部分靠手动操作比如教师开始签到后改成 ONGOING另一部分靠定时任务自动判断比如报名截止时间到了自动把 OPEN 改成 ENDED。Spring Boot 的 Scheduled 注解是实现定时任务最简单的方案。在启动类或者配置类上加上 EnableScheduling然后在 Service 方法上写 Scheduled(cron 0 */5 * * * ?)表示每 5 分钟执行一次扫描。定时任务里做的事情可以包括把开始时间已过的课程状态从 OPEN 自动改为 CLOSED把结束时间已过的课程状态从 ONGOING 改为 FINISHED给课程开始前 24 小时仍未退选的学生发送站内通知。这里有个定时任务并发执行的小坑默认情况下 Spring 的 Scheduled 是单线程串行执行的如果你的任务里写了 Thread.sleep 或者调用了耗时的发送短信接口会阻塞后续任务。解决方式是在配置类里自定义一个 TaskScheduler Bean把线程池大小设为 4 或 5。这个细节写进论文里是加分项。4. 常见问题与排查技巧帮你在开发调试阶段少掉头发4.1 并发抢课的三大典型故障做完系统第一次联调测试我敢说你大概率会遇到下面三个问题。第一个是重复选课前端点击多次导致一条选课记录插了两次数据表出现脏数据。解决办法是两层防护前端按钮加 loading 状态防连点后端在 selection 表建联合唯一索引。第二个是超卖count 判断通过但实际 insert 时名额已经没了。解决办法用悲观锁或乐观锁具体操作在前面已经讲过。第三个是死锁select for update 的加锁顺序不一致导致互相等待例如一个接口先锁 activity 再锁 user另一个接口先锁 user 再锁 activity。解决办法是统一加锁顺序先锁 activity 表再锁其他表。4.2 前后端联调时最容易翻车的三个接口问题联调阶段最常见的一个问题是跨域。前端在 8080 端口后端在 9000 端口浏览器直接拦截请求页面怎么点都不通。解决办法是写一个 WebMvcConfigurer 配置类重写 addCorsMappings 方法设置允许跨域或者使用 CrossOrigin 注解。第二个问题是日期格式前端传的字符串日期和后端 LocalDateTime 格式不一致导致解析失败。我建议统一用 JSON 序列化配置在 application.yml 里设置 spring.jackson.date-format 和 spring.jackson.time-zone并在前端统一传 yyyy-MM-dd HH:mm:ss 格式的字符串。第三个问题是返回结构不统一有的接口返回 Result 对象有的接口直接返回 List前端处理起来非常痛苦。强烈建议所有接口统一返回 Result 封装包含 code、message、data 三个字段。4.3 常见 Bug 速查表我把实际调试中碰到的高频问题整理成一张表对应症状和排查方向方便你出问题时快速定位症状可能的根因排查方向登录后访问其他接口返回 401拦截器 exclude 路径没有放行登录接口检查 WebMvcConfigurer 的排除路径配置选课成功但名额没变事务只对插入生效更新语句被遗漏检查 Service 方法是否有 update selected_count定时任务不执行启动类缺少 EnableScheduling检查启动类和配置类注解接口返回时间差了 8 小时时区配置不对在 JDBC URL 加 serverTimezone 参数并检查数据库时区前端报错Invalid CORS request跨域配置的 allowedOrigin 写错用 allowedOriginPatterns 替代支持跨域携带凭证数据库插入中文乱码连接串没指定编码JDBC URL 加 useUnicodetruecharacterEncodingutf8有一个经验想说一下排查这类问题不要一上来就怀疑框架或环境按前端请求参数—后端接口日志—SQL 执行结果的顺序从前往后查大部分问题能在一个小时内定位。尤其是 Spring Boot控制台会打印非常详细的启动日志和请求日志第一眼先看有没有堆栈异常再看业务逻辑是否进到了你预期的分支乱改配置反而浪费时间。5. 论文撰写与演示准备功能做完了别在呈现上丢分5.1 论文结构安排从绪论到测试用满五个章节如果你的题目是毕业设计系统的代码只是工程量的一半论文是同样重要的一半。常见的论文结构是摘要、绪论、相关技术、系统分析、系统设计、系统实现、系统测试、总结。相关技术这一章重点写 Spring Boot、MyBatis、JWT、Vue 或 Thymeleaf注意不要写成名词堆砌要把它们在本项目里承担的具体职责讲清楚。系统分析写可行性分析和需求分析系统设计写架构设计、功能模块和数据库设计系统实现里按登录、选课、学分认定几个页面逐一展示并附上关键代码和运行截图。需要留意的是数据库设计中一定要画出 E-R 图。有些同学画不好 E-R 图直接用 Word 文本框一个个拼画得歪歪扭扭。教大家一个技巧用 draw.io 这类工具先选中实体类、属性、关系三个元素画完导出 PNG 再插入文档里整体会比手工绘制规整得多。数据表说明书也不要遗漏每个字段的名称、类型、是否主键、含义注释写清楚这部分是答辩评委重点翻阅的内容。5.2 演示环节最加分的五个操作最后答辩演示时强烈建议按这五个操作流程走一遍覆盖全部核心功能一是登录管理员账号创建一个分类和一门活动课程并审核通过二是登录学生账号浏览课程、选课、再退选三是选课成功后查看个人已选列表和学分页面四是登录教师账号对已经选课的学生进行签到五是回到管理员端查看统计报表如果有多条件筛选可以现场演示筛选效果。每一步操作尽量把 URL 的变化、数据库的响应说明清楚比如选中课程后 selection 表新增了一条记录activity 表的 selected_count 从 20 变成了 21这种细节能让评委立刻相信系统是能真实运行的而不仅仅是一个空壳表皮。关键页面的截图建议提前在理想分辨率下截好不要在答辩现场临时打开系统万一网络或者环境出问题会很被动。重要逻辑最好准备一个简单的时序图或流程图用 Word 里的基本形状绘制足够不需要搞复杂的工具重点是展示你的逻辑推导能力。我见过不少同学代码写得一般但是流程图清楚、讲解顺畅最终成绩反而不差这是选题和演示中很值得投入的一块。6. 进阶扩展方向做完基础版之后还能往哪些方向加分基础功能全部跑通之后如果你还有余力可以挑一个方向做深入扩展。我比较推荐的方向有三个第一个是消息通知模块用 WebSocket 实现系统站内信当课程审核通过、选课成功、退选成功时实时通知用户前端做一个消息中心第二个是数据可视化用 ECharts 给管理员端加一个仪表盘展示课程热度排行、各院系参与率、学时分布饼图等页面效果立刻高级一大截第三个是导入导出功能用 EasyExcel 或 POI 把学生选课名单、学分认定汇总生成 Excel方便教务线下存档。这三个扩展方向难度递增但技术含金量也都对应提高。如果你时间比较紧选第一个方向就够WebSocket 集成在 Spring Boot 里写起来并不复杂核心类就是一个 WebSocketHandler 和几个消息实体再加上前端 onopen、onmessage 两个方法效果却很明显。如果你学有余力可视化方向对答辩更有冲击力评委看到图表会下意识觉得你代码能力不差。最后再提醒一个小技巧不管选哪个扩展方向记得在论文和 PPT 里提前埋下伏笔——把系统后续可扩展方向写进总结章节然后在演示时顺势提一句这个模块我采用了某某技术目前已经实现了基础版本这样整个项目的完整度和深度都会往上走一个台阶。
返回列表