
简介这是一套面向高校学生网上选课场景的Java毕业设计项目采用Spring Boot与Vue的前后端分离架构覆盖教室管理、老师管理、课程管理、教学计划管理、选课管理、成绩管理、学生管理等功能模块可有效解决传统人工选课信息维护困难、流程繁琐等痛点适合计算机相关专业做毕设参考或Java Web开发进阶练习。压缩包内共有402个文件体积13.32MB其中包含109个Java后端源文件、56个Vue前端页面、161个SVG图标以及JS/CSS/XML等配置资源、SQL数据库脚本和安装、构建、运行批处理脚本便于直接部署运行。资源已有88人学习。下载后能获得完整可运行的前后端代码、数据库初始化脚本和LW配套说明文档项目结构清晰从安装依赖、编译构建到启动服务均有脚本指引有助于理解Spring Boot与Vue的接口交互、数据访问逻辑和选课业务流程适合直接用于毕设演示、功能二次开发或论文撰写参考。 又是课设季我后台已经收到好几个学生网上选课系统的求助了。说实话选课系统几乎是计算机科学与技术、软件工程专业里出现频率最高的课程设计题目没有之一。但奇怪的是网上一搜一大把代码数据库LW的压缩包答辩时能讲清楚自己系统怎么设计的人却少得可怜。问题不是代码量不够而是很多人拿到的项目要么过度设计、要么表结构混乱遇到为什么这么设计的提问就露馅。这篇文章我想从一个完整的课设视角把这个题目拆开揉碎讲一遍。重点不是给你一份现成代码而是告诉你一个能通过答辩的选课系统需求该盘到什么程度、表该怎么建、代码里哪些点才是老师眼里设计和实现的真正价值以及LW也就是设计说明文档/论文到底怎么组织才靠谱。如果你正在做这个题目或者想拿它当练手项目这篇可以帮你少走很多弯路。1. 选课系统到底在选什么先把业务规则盘清楚很多人拿到题目就开写一张student表一张course表一张选课表三个增删改查页面一拼以为完事了。结果老师一问你考虑过同一时间段的课程冲突吗课程人数满了怎么处理人就愣住了。选课系统的核心从来不是CRUD而是业务规则。1.1 三类角色先说透学生、教师、管理员这是选课系统的基本面。学生负责浏览课程、选课、退课、查看已选课程教师负责开课、查看选课名单、录入成绩管理员负责维护基础数据、审核开课信息、发布公告。权限控制不需要上Shiro或者Spring Security这种框架课设阶段一张用户表加一个role字段就够用。你要是想演示一下拦截器可以在后端写一个简单的登录拦截根据role判断接口是否可访问。别为了显得高级硬塞权限框架到时候写的代码自己都解释不清楚反而扣分。1.2 容易被忽略的隐藏规则我见过太多选课系统的实现能把课程列表展示和选课按钮点通就交差了。但一个正经的选课场景至少要覆盖以下规则同一个学生不能在同一时间选两门课时间冲突校验课程选课人数达到容量上限后不能继续选课同一门教学班不能被同一个学生重复选退课后名额要释放出来其他学生才能选可选先修课程校验、选课时间窗口限制不要小看这四条。第1条和第3条大多数人都想到了第2条做的时候容易忽略并发场景第4条经常有人忘记在退课接口里加名额回补逻辑。这些规则基本决定了数据库表怎么设计所以我建议先把规则写在需求文档里再动数据库。1.3 状态变化也要设计选课系统里有两类状态容易忽略。一类是课程教学班的状态未开始选课、选课中、选课结束、已结课。另一类是选课记录的状态已选、已退选、成绩已录入。状态字段建议都放数据库里用tinyint类型存数字就行。别用字符串存已选已退这种中文值一是查询效率低二是后续想改状态语义的成本高。我在课设里通常预留一个status字段用0、1、2这样的数字表示代码里配一个枚举类解释含义这样论文里写状态设计也有东西可说。2. 数据库是系统的地基表结构设计的关键决策数据库设计是这个项目的灵魂。如果表建错了后面代码写得再漂亮也救不回来。这一节我把选课系统的核心表结构和几个关键设计决策讲清楚。2.1 六张核心表就够用以下这六张表是选课系统的标准配置我建议你照着这个思路去建表名作用关键字段student学生信息id, sno, name, password, major, class_name, gradeteacher教师信息id, tno, name, password, departmentadmin管理员信息id, username, passwordcourse课程基础信息id, cno, name, credit, course_type, departmentcourse_arrangement教学班某学期某教师开设的课程id, course_id, teacher_id, semester, day_of_week, start_section, end_section, capacity, selected_count, statusselection_record选课记录id, student_id, arrangement_id, create_time, status核心表就这六张。课程和教学班为什么要拆成两张表这是下面要讲的重头戏。2.2 为什么课程表必须和教学班分离这是选课系统数据库设计里最容易理解错的一步。很多初学者会把课程信息、开课学期、上课老师、上课时间全塞进一张course表里然后一门课开两个学期就复制出两行数据——这完全是个坑。课程是稳定信息课程代码、课程名称、学分这些属于课程本身课程表里存一份就够。但2024年秋季学期张三老师周一3-4节开的软件工程是一个教学班它不是课程。同一个课程代码秋季开一个班、春季开一个班就是两条教学班记录。把课程和教学班分开之后容量、当前选课人数、上课时间、授课教师都挂在course_arrangement上。这样设计的好处是改老师的上课时间不会影响课程基础信息查某个学期开了哪些课也简单历史选课数据也不会因为课程信息修改而乱套。2.3 选课记录表的核心约束联合唯一索引选课记录表是学生和教学班之间的关联表最容易出问题。为了防止同一个学生对同一个教学班重复选课我强烈建议建一张联合唯一索引CREATE TABLE selection_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, arrangement_id BIGINT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0 COMMENT 0-已选 1-已退选 2-已录入成绩, UNIQUE KEY uk_student_arrangement (student_id, arrangement_id) );这个联合唯一索引是整个系统的安全底线。即使你的后端代码逻辑写漏了判断数据库层面也能兜住重复插入。答辩时老师问你怎么防止重复选课你就说两层代码里先查一次数据库层面再用唯一索引兜底。这个回答很加分。外键方面课设阶段我建议老老实实建物理外键因为老师翻了你的数据库设计文档看到关系清晰的ER图印象分就上去了。实际项目里确实很多人用逻辑外键但课设不需要对标工业级方案物理外键的直观性能帮你省很多解释成本。2.4 容量字段怎么设计冗余selected_count课程容量和已选人数我建议直接在course_arrangement表里加两个字段capacity和selected_count。selected_count是一个冗余字段每次选课成功就在事务里加1退课就减1。为什么不每次都去selection_record表里count一下因为查列表页的时候每门课都要显示已选/容量count查询走一遍开销大而且需要join接口响应会慢。用冗余字段的话列表查询只需要查course_arrangement一张表。当然冗余字段也有代价就是并发一致性问题。这个我在第4章专门讲现在你只需要记住capacity是上限selected_count是当前已选人数check约束直接写在service层判断逻辑里。3. 技术栈与代码骨架什么好上手、什么能答辩技术选型这件事课设和实战项目的逻辑刚好相反。实战项目要考虑团队协作、部署环境稳定性、业务规模课设考虑的是能不能独立写完、代码能不能讲清楚、答辩能不能扛住追问。3.1 我为什么推荐 SpringBoot Vue 的组合后端用SpringBoot MyBatis或者MyBatis-Plus前端用Vue ElementUI或者Element Plus数据库用MySQL 8.0这是目前课设里最主流的方案也是我认为教育资源最丰富、上手成本最低的组合。SpringBoot的好处是启动快、配置少、社区资料多基本上你遇到的问题都能搜到答案。MyBatis-Plus比原生MyBatis更省事内置的BaseMapper已经帮你把单表CRUD写好了你只需要专注写业务逻辑。前端Vue生态成熟ElementUI的表格、表单、弹窗组件能让你在几天内拼出一个看起来不错的界面。至于SpringCloud微服务、Redis缓存、消息队列这些我的建议是别加。选课系统用数据库事务完全能解决业务问题你加个MQ老师反问一句为什么需要消息队列你就得解释一堆而且大概率暴露你只是在灌概念。3.2 后端代码怎么分层controller / service / mapper后端包的划分我建议按照主流的三层架构来controller包接收参数、调用service、返回结果不写业务逻辑service包业务规则都在这一层实现比如冲突检测、名额扣减mapper包数据访问层写SQL或者用MyBatis-Plus的BaseMapperentity包数据库表对应的实体类dto/vo包接收前端参数的对象、返回前端展示的对象config包跨域配置、拦截器配置等Controller层为什么不要写业务逻辑因为答辩的时候老师会让你画系统架构图你要是把业务逻辑都堆在Controller里图都不好画。任何编程规范里都强调职责分离这是设计能力的体现。3.3 前端页面最少做几个前端页面不需要贪多把这几页做出来功能闭环就成立了登录页角色选择登录课程列表页展示所有教学班支持按课程名/教师搜索显示容量和已选人数提供选课按钮我的选课页展示已选课程提供退课按钮教师端开课页教师创建教学班填写时间地点容量教师端已选名单页查看选了课的学生列表管理端用户管理页管理员维护学生、教师、课程基础数据就这六页跑通一遍完整的选课、退课、开课流程你的课程设计已经超过90%的人了。剩下的时间不如用来打磨细节和写文档。4. 选课冲突与并发控制系统最容易被问倒的环节选课系统的含金量集中在冲突检测和并发控制这两个点上。这两个点也是答辩时老师最喜欢深入问的地方因为它们涉及数据库隔离级别、事务、索引这些核心概念。4.1 时间冲突检测怎么设计才简单可靠课程时间冲突的检测核心在于上课时间的存储方式。我推荐用三个字段来存day_of_week周几1-7、start_section开始节次、end_section结束节次。比如周一第三四节有课就存day_of_week1, start_section3, end_section4。选课前的冲突检测逻辑分两步先查出学生所有已选教学班的day_of_week、start_section、end_section判断是否与新选课程在同一天有节次交集-- 查询学生已选课程的时间安排 SELECT ca.day_of_week, ca.start_section, ca.end_section FROM course_arrangement ca JOIN selection_record sr ON ca.id sr.arrangement_id WHERE sr.student_id #{studentId} AND sr.status 0;然后在代码里做交集判断同一天的情况下新课程的start_section小于等于已有课程的end_section并且新课程的end_section大于等于已有课程的start_section就说明冲突了。这个逻辑用一次循环就能完成不需要什么复杂算法。你甚至可以写一个单独的工具方法之后写单元测试也方便。4.2 并发选修同一门课超卖问题才是分水岭假设最后一门课只剩1个名额这时候100个学生同时点击选课会发生什么如果代码是这样写的——先查selected_count再判断是否小于capacity最后执行插入——那就必然超卖。因为多个请求同时读到selected_count99都认为还有名额全部通过了校验。解决思路本质上就是让检查并更新名额变成一个原子操作。这里我推荐两种方案都足够实用。方案一乐观锁扣减。直接用一个UPDATE语句在条件里带上容量判断UPDATE course_arrangement SET selected_count selected_count 1 WHERE id #{arrangementId} AND selected_count capacity;这个语句返回的影响行数如果等于1说明扣减成功名额占住了下一步插入选课记录。如果影响行数是0说明名额已经满了直接返回课程已满。这个方案简单高效不需要锁表也是我最推荐课设采用的方式。方案二事务内悲观锁。在事务里先SELECT ... FOR UPDATE锁住教学班这一行再检查容量再更新-- 事务开始 SELECT capacity, selected_count FROM course_arrangement WHERE id #{arrangementId} FOR UPDATE; -- 然后在代码里判断 selected_count capacity -- 更新名额 插入选课记录 -- 事务提交FOR UPDATE会对这一行加排他锁其他事务的相同查询会被阻塞直到当前事务提交。这样也能保证不会超卖。缺点是并发高的时候性能不如乐观锁但课设阶段两者都能用。我个人的建议是方案一为主数据库唯一索引兜底。也就是先执行那个带条件的UPDATE如果成功再插入选课记录插入时若命中了联合唯一索引导致DuplicateKeyException就说明重复选课回滚名额。4.3 事务不生效的三个真实原因关于事务我审过不少课设代码发现Transactional失效的情况特别常见而且基本都是这三个原因方法加在非public方法上Spring代理不生效同一个类内部调用比如Controller调用本类的另一个方法绕过了代理对象有加Transactional但方法内部用try-catch把异常吞了事务感知不到错误自然就提交了还有一个隐藏的坑MySQL表用了MyISAM存储引擎。MyISAM不支持事务也不支持外键你在建表的时候没指定ENGINEInnoDB或者建库用了默认的MyISAM那事务就白写了。检查一下建表语句里有没有ENGINEInnoDB没用的话全部加上。4.4 这点并发为什么要用数据库而不是Redis老师可能会问选课系统并发高的时候用数据库扛得住吗为什么不用Redis做库存扣减这个问题你可以这样回答学生选课的并发量级并没有到电商秒杀那种程度数据库事务配合UPDATE条件判断已经能正确解决超卖问题引入Redis虽然能提升吞吐但也带来了缓存和数据库一致性的复杂度对于一个课设项目来说属于过度设计。如果之后想扩展再考虑Redis也不迟。这个回答既体现了你的思考又不会给自己挖坑。5. LW设计文档的写作框架与答辩准备标题里的LW其实就是论文的拼音缩写放在课设语境下就是设计文档或者说课程设计报告。代码和数据库做完之后LW是决定你最终成绩下限的关键。我见过系统做得不错但文档写成流水账最后拿低分的也见过项目一般但文档逻辑清晰、图表规范拿优的。文档一定要认真对待。5.1 课设报告和毕业论文的差别课设报告的体量比毕业论文小通常两三万字以内就够了。毕业论文讲究学术规范要有绪论、国内外研究现状、核心技术的理论分析课设报告更偏工程实践——你做了什么系统、怎么设计的、怎么实现的、测试结果如何。所以别照着毕业论文的框架硬套那会让老师觉得你在凑字数。5.2 一份合格文档的章节骨架下面这个结构是我认为最适合选课系统课设的需求分析写清楚题目背景、三类角色的功能需求、业务规则选课规则、退课规则最好配上用例图系统设计技术架构SpringBoot Vue MySQL、功能模块划分、数据库设计ER图 所有表的字段说明系统实现每个功能模块的关键代码和界面截图难点部分并发控制、冲突检测重点展开系统测试功能测试用例表 测试截图覆盖正常选课、重复选课、满员选课等场景总结与展望自己做这个项目学到了什么系统的不足之处和未来扩展方向这套结构基本对应了设计和实现两个字评审老师挑不出大毛病。5.3 哪些图必须画必画的图有四张用例图、ER图、功能模块图、技术架构图。用例图在需求分析章节展示三类角色能做什么操作ER图在数据库设计章节展示六张核心表以及表之间的关联关系功能模块图展示系统的功能树技术架构图展示前端、后端、数据库三层结构。画图工具用draw.io、ProcessOn或者Visio都可以手画也行关键是要规范。表之间的1对多、多对多关系要标注清楚因为老师会根据ER图去核对你的表结构。另外数据库章节我建议给每张表加一个字段说明表字段名、类型、是否主键、是否外键、含义一目了然。这能省去答辩时被追问字段含义的麻烦。5.4 代码不是贴得越多越好文档里贴代码是有策略的全贴等于没贴。我建议只贴三类代码建表SQL尤其是带唯一索引和容量判断条件的语句选课事务方法展示并发控制和回滚逻辑时间冲突检测的工具方法每段代码后面都要配文字解释说明这段代码为什么这么写解决了什么问题。这比贴十个增删改查方法有价值得多。5.5 答辩前必背的几个问题答辩老师大概率会从下面几个方向里挑问题提前准备就不会慌课程和教学班为什么分成两张表联合唯一索引是干什么用的怎么防止选课人数超过容量事务注解加在方法上为什么不生效的场景有哪些这个系统有什么可以继续优化的地方最后一题别回答没有你可以说分页查询可以做进一步的性能优化、密码应该加密存储、加Redis应对极端选课高峰这些都是可接受的改进方向说明你有思考。6. 开发到部署的实战坑乱码、跨域、环境排查最后写点实操中几乎每个学生都会踩的坑。这些坑我第一次做课设的时候都踩过当时排查一个中文乱码问题花了整整一个下午。6.1 中文乱码问题中文乱码九成出在数据库连接串上。SpringBoot的application.yml里JDBC连接串一定要带编码参数spring: datasource: url: jdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai另外建数据库的时候要指定字符集CREATE DATABASE course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;用utf8mb4而不是utf8的原因是utf8mb4是真正的四字节字符集能存emoji和生僻字而且兼容utf8。前端页面如果也有乱码检查一下HTML文件的meta标签charset是不是utf-8。就这三处查完基本能解决所有中文乱码。6.2 前后端分离的跨域问题用Vue做前端、SpringBoot做后端本地开发的时候默认就跨域。浏览器控制台会报Access-Control-Allow-Origin相关的错。最省事的解决办法是在后端加一个全局CORS配置类允许所有来源Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }allowedOriginPatterns(*)是允许任意来源开发阶段够用了。实际部署的时候如果你用Nginx做反向代理把前端和后端放到同一个域名下让Nginx把/api开头的请求转发给后端服务跨域问题也就不存在了。这个可以作为你论文展望里的一句话说一下体现你考虑过生产部署。6.3 LocalDateTime 前后端传参的序列化问题用了LocalDateTime之后前端经常收到类似2024-05-06T10:15:30这种带T的格式或者前端传时间字符串后端解析不了。给时间字段加一个统一的格式化注解就行JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private LocalDateTime createTime;注意时区要指定GMT8否则可能出现时间比北京时间早8小时的情况。这个坑很多人遇到但不明白为什么其实就是时区没设置。6.4 数据库连不上的几个排查步骤如果你启动项目后报数据库连接失败我建议按这个顺序排查MySQL服务有没有启动在命令行执行systemctl status mysql或者直接看服务列表用户名密码对不对用Navicat或者MySQL Workbench先手动连一次试试数据库名对不对看application.yml里的url写的库名和实际进的库名是否一致依赖版本对不对spring-boot-starter-parent版本和mysql-connector-java版本要兼容我用的是MySQL 8.0配mysql-connector-j:8.0.33没问题时区问题报错信息里如果出现Server Time Zone或者UTC字样就把serverTimezoneAsia/Shanghai加到连接串里还有一个小建议答辩前用mysqldump把数据库备份一下。我自己以前帮同学看过一个项目演示前一天手滑把选课记录表清空了没有备份直接傻眼。这条命令能救命mysqldump -u root -p course_selection course_selection_backup.sql到时候想恢复就执行mysql -u root -p course_selection course_selection_backup.sql这个动作三分钟就能做完但是能避免演示现场翻车强烈建议养成习惯。选课系统这个题目说难不难说简单也不简单。真正把它做完做透你会发现它几乎覆盖了JavaWeb开发的所有基本功SpringBoot的核心用法、MyBatis的SQL编写、MySQL的表关系设计、事务与并发控制、Vue组件化开发、前后端联调。我个人的体会是课设阶段不用追求技术栈花哨把一个业务系统的边界、规则、异常情况都想清楚把每一行贴到文档里的代码都吃透答辩的时候你就有底气。希望这篇能帮你在DDL之前少走几个坑做完之后不仅交了差还真正长了本事。本文还有配套的精品资源点击获取