1. 项目背景与核心需求
在大学教务管理体系中,选课系统是连接教学资源与学生需求的核心枢纽。传统选课方式往往面临服务器崩溃、界面卡顿、数据不同步等痛点。基于SSM(Spring+SpringMVC+MyBatis)与VUE.js的前后端分离架构,恰好能解决这些痛点。
这个选课系统设计需要满足三类角色的核心诉求:
- 学生端:课程查询/选课/退课/课表生成(支持冲突检测)
- 教师端:开课申请/成绩录入/班级管理
- 管理员端:课程管理/权限分配/系统监控
提示:实际开发中建议采用RBAC权限模型,通过角色表(user_role)、权限表(role_permission)实现细粒度控制
2. 技术栈选型解析
2.1 后端技术组合
采用经典SSM框架组合:
- Spring 5.3:IoC容器管理Bean生命周期,AOP处理事务(@Transactional注解控制选课原子性)
- SpringMVC:RESTful接口设计,异常统一处理(@ControllerAdvice捕获选课冲突异常)
- MyBatis 3.5:XML映射文件编写复杂SQL,例如:
xml复制<select id="selectAvailableCourses" resultMap="courseResult">
SELECT * FROM course
WHERE capacity > (SELECT COUNT(*) FROM selection WHERE course_id=course.id)
AND semester = #{currentSemester}
</select>
2.2 前端技术方案
VUE 3.x带来显著优势:
- Composition API:将选课逻辑封装为useCourseSelection()可复用hook
- Vue Router:嵌套路由实现多级菜单(/student/course/selection)
- Axios:封装拦截器处理401跳转登录页
- Element Plus:采用表格虚拟滚动优化万级课程列表渲染
3. 数据库关键设计
3.1 核心表结构
sql复制CREATE TABLE `course` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '课程名',
`teacher_id` int NOT NULL,
`credit` tinyint NOT NULL COMMENT '学分',
`capacity` int NOT NULL COMMENT '容量',
`selected` int DEFAULT '0' COMMENT '已选人数',
`semester` varchar(20) NOT NULL COMMENT '2023-2024-1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `selection` (
`id` int NOT NULL AUTO_INCREMENT,
`student_id` int NOT NULL,
`course_id` int NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`score` decimal(5,2) DEFAULT NULL COMMENT '成绩',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_course` (`student_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 事务处理要点
选课操作需包含三个原子步骤:
- 检查课程剩余容量
- 插入选课记录
- 更新已选人数计数
Spring事务配置示例:
java复制@Transactional(rollbackFor = Exception.class)
public boolean selectCourse(Integer studentId, Integer courseId) {
if(courseMapper.checkCapacity(courseId) <= 0){
throw new BusinessException("课程已满");
}
if(selectionMapper.existsSelection(studentId, courseId)){
throw new BusinessException("不可重复选课");
}
selectionMapper.insert(new Selection(studentId, courseId));
return courseMapper.incrementSelected(courseId) > 0;
}
4. 典型功能实现详解
4.1 选课冲突检测
前端采用Timetable.js可视化展示已选课程时段,后端校验逻辑:
java复制public void checkTimeConflict(Integer studentId, Course newCourse) {
List<Course> selectedCourses = courseMapper.listSelected(studentId);
selectedCourses.forEach(course -> {
if(course.getWeekday().equals(newCourse.getWeekday())){
if(!(course.getEndTime() <= newCourse.getStartTime()
|| course.getStartTime() >= newCourse.getEndTime())){
throw new BusinessException("时间冲突:"+course.getName());
}
}
});
}
4.2 高并发选课优化
采用Redis+Lua脚本解决选课超卖问题:
lua复制local key = 'course:'..KEYS[1]
local capacity = tonumber(redis.call('HGET', key, 'capacity'))
local selected = tonumber(redis.call('HGET', key, 'selected'))
if selected < capacity then
redis.call('HINCRBY', key, 'selected', 1)
return 1
end
return 0
5. 部署与性能调优
5.1 前端部署方案
- 使用nginx配置gzip压缩和静态缓存
- 路由history模式配置try_files规则:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
5.2 后端性能保障
- JVM参数调优:-XX:+UseG1GC -Xmx1024m
- Tomcat连接池配置:
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
6. 毕设开发心得
- 文档先行:先画UML时序图理清选课流程,再编码实现
- 测试策略:使用MockMvc测试Controller层,保证接口契约稳定
- 性能陷阱:避免N+1查询问题,MyBatis配置懒加载需谨慎
- 前端技巧:使用Vuex持久化插件保存用户登录状态
注意:成绩录入功能建议采用乐观锁机制,防止教师并发修改冲突
我在实际开发中发现,选课系统的压力测试需要特别关注以下场景:
- 热门课程开放选课时瞬间流量
- 成绩录入截止前教师集中操作
- 学期初学生批量查询课表
建议使用JMeter模拟500并发用户测试,观察数据库连接池使用情况。可以适当引入消息队列削峰填谷,比如使用RabbitMQ延迟队列处理退课后的名额释放
