1. 项目背景与核心需求
高校学生选课系统是计算机专业毕业设计中的经典选题之一。作为一个完整的Web应用,它涵盖了用户管理、课程管理、选课逻辑、冲突检测等核心功能模块,非常适合用来展示全栈开发能力。选择SpringBoot作为技术栈,主要基于以下几个考量:
- 快速开发特性:SpringBoot的自动配置和起步依赖能大幅减少XML配置,让开发者更专注于业务逻辑实现
- 微服务友好:内置Tomcat容器和RESTful支持,方便后续扩展为分布式系统
- 生态丰富:MyBatis、JPA、Security等成熟组件可直接集成
- 就业优势:SpringBoot是企业Java开发的主流框架,完成这个项目能积累实战经验
提示:选课系统看似简单,但要做出亮点需要在前端交互、并发控制、数据一致性等方面下功夫。建议在基础功能之外,至少实现1-2个技术难点作为项目特色。
2. 技术选型与架构设计
2.1 基础技术栈组合
- 后端框架:SpringBoot 2.7.x + Spring MVC
- 持久层:MyBatis-Plus 3.5.x(含分页插件)
- 数据库:MySQL 8.0(事务型) + Redis(缓存)
- 前端:Vue 3 + Element Plus(推荐前后端分离)
- 构建工具:Maven/Gradle
- 部署:Docker容器化
2.2 分层架构设计
└── src/main/java ├── config # 配置类 ├── controller # 表现层 ├── service # 业务逻辑层 ├── dao # 数据访问层 ├── entity # 实体类 ├── dto # 数据传输对象 ├── util # 工具类 └── exception # 异常处理2.3 数据库ER图关键表
CREATE TABLE `student` ( `id` varchar(20) PRIMARY KEY, `name` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `department` varchar(50) COMMENT '院系' ); CREATE TABLE `course` ( `id` int AUTO_INCREMENT PRIMARY KEY, `name` varchar(100) NOT NULL, `teacher_id` int NOT NULL, `credit` tinyint NOT NULL, `capacity` int NOT NULL COMMENT '容量', `remain` int NOT NULL COMMENT '剩余名额' ); CREATE TABLE `selection` ( `id` int AUTO_INCREMENT PRIMARY KEY, `student_id` varchar(20) NOT NULL, `course_id` int NOT NULL, `select_time` datetime NOT NULL, UNIQUE KEY `uk_stu_course` (`student_id`,`course_id`) );3. 核心功能实现细节
3.1 选课业务逻辑实现
选课是系统最核心的并发敏感操作,需要特别注意线程安全:
@Transactional public boolean selectCourse(String studentId, Integer courseId) { // 1. 校验选课资格 if(selectionMapper.existsByStudentAndCourse(studentId, courseId)){ throw new BusinessException("不能重复选课"); } // 2. 使用乐观锁扣减名额 int updated = courseMapper.decreaseRemainWithLock( courseId, "remain > 0" // 乐观锁条件 ); if(updated == 0){ throw new BusinessException("课程已满"); } // 3. 创建选课记录 Selection record = new Selection(); record.setStudentId(studentId); record.setCourseId(courseId); record.setSelectTime(LocalDateTime.now()); return selectionMapper.insert(record) > 0; }3.2 时间冲突检测算法
课程冲突检测是选课系统的关键算法,推荐使用时间段重叠判断:
public boolean checkTimeConflict(String studentId, Course newCourse) { List<Course> selected = courseMapper.selectByStudent(studentId); return selected.stream().anyMatch(existing -> existing.getWeekDay() == newCourse.getWeekDay() && !(existing.getEndTime() <= newCourse.getStartTime() || existing.getStartTime() >= newCourse.getEndTime()) ); }3.3 高并发优化方案
选课高峰期可能出现秒杀场景,建议采用以下优化措施:
- Redis缓存预热:提前加载热门课程信息到Redis
- 分布式锁:使用Redisson实现选课操作的互斥
- 队列削峰:RabbitMQ异步处理选课请求
- 限流措施:Guava RateLimiter控制接口QPS
4. 典型问题与解决方案
4.1 选课超卖问题
现象:多个学生同时选课导致剩余名额出现负数
解决方案:
- 数据库层面:使用乐观锁(version字段或条件更新)
- 应用层面:同步代码块或分布式锁
- 最终方案:Redis原子操作 + 数据库乐观锁双重校验
4.2 事务失效场景
常见原因:
- 方法非public
- 自调用(this.xxx())
- 异常被捕获未抛出
- 数据库引擎不支持(如MyISAM)
正确写法示例:
@Transactional(rollbackFor = Exception.class) public void batchSelect(String studentId, List<Integer> courseIds) { courseIds.forEach(courseId -> { try { selectCourse(studentId, courseId); } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(); throw e; } }); }4.3 性能优化实践
- N+1查询问题:使用MyBatis的
<collection>标签或@One注解 - 分页优化:MyBatis-PageHelper配合count查询缓存
- SQL调优:为student_id、course_id等字段添加索引
- 连接池配置:合理设置Druid的maxActive、minIdle参数
5. 项目扩展与亮点设计
5.1 可视化排课功能
使用ECharts实现课表可视化展示:
// Vue组件示例 <template> <div ref="chart" style="width:100%;height:400px"></div> </template> <script> import * as echarts from 'echarts'; export default { mounted() { const chart = echarts.init(this.$refs.chart); chart.setOption({ tooltip: { /*...*/ }, calendar: { /*...*/ }, series: [{ type: 'heatmap', data: this.generateScheduleData() }] }); } } </script>5.2 智能推荐算法
基于协同过滤的课程推荐:
- 收集历史选课数据
- 计算课程相似度矩阵
- 实现推荐接口:
public List<Course> recommendCourses(String studentId) { // 1. 获取同院系学生的选课数据 List<Selection> similarSelections = selectionMapper .selectBySimilarStudents(studentId); // 2. 统计高频课程 Map<Integer, Long> courseFrequency = similarSelections.stream() .collect(Collectors.groupingBy( Selection::getCourseId, Collectors.counting() )); // 3. 返回TopN推荐 return courseFrequency.entrySet().stream() .sorted(Map.Entry.comparingByValue().reversed()) .limit(5) .map(entry -> courseMapper.selectById(entry.getKey())) .collect(Collectors.toList()); }5.3 多端适配方案
- 微信小程序端:使用Uniapp跨端框架
- 管理后台:Vue Element Admin模板
- API设计:Swagger + Postman自动化测试
6. 部署与监控方案
6.1 Docker容器化部署
推荐使用docker-compose编排服务:
version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql redis: image: redis:6 ports: - "6379:6379" app: build: . ports: - "8080:8080" depends_on: - mysql - redis6.2 SpringBoot Admin监控
配置步骤:
- 添加依赖:
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.7.0</version> </dependency>- 主类添加注解:
@EnableAdminServer @SpringBootApplication public class AdminApplication { public static void main(String[] args) { SpringApplication.run(AdminApplication.class, args); } }- 客户端配置:
spring.boot.admin.client.url=http://localhost:8080 management.endpoints.web.exposure.include=*7. 毕设答辩准备建议
技术亮点提炼:
- 突出解决的核心难题(如并发控制)
- 展示性能优化前后的对比数据
- 演示扩展功能(如智能推荐)
常见问题准备:
- 为什么选择SpringBoot而不是SSM?
- 系统能支持的最大并发量是多少?
- 如果选课时间冲突怎么处理?
- 如何保证数据一致性?
文档规范:
- 架构图使用PlantUML绘制
- 接口文档用Swagger UI展示
- 数据库设计提供ER图
- 关键算法给出流程图
我在实际开发中发现,选课系统最容易被忽视的是异常处理场景。比如网络中断时的选课状态回滚、缓存与数据库的一致性维护等。建议在测试阶段专门设计异常测试用例,包括:
- 连续快速点击选课按钮
- 选课过程中断网
- 服务器重启后数据恢复
- 批量选课部分成功场景