1. 项目概述:当教务系统遇上互联网+
教务管理中最让人头疼的选课环节,相信每个大学生都深有体会。凌晨蹲守机房抢课、系统崩溃时的绝望、人工调剂的手忙脚乱...这些场景即将成为历史。今天要分享的这套选课系统,用Java+SSM做后端核心,Django处理管理端业务,实现了从"抢课"到"智能选课"的进化。
这个系统最核心的价值在于:把传统教务选课中所有线下流程(课表查询、选课冲突检测、人数控制、成绩关联)全部数字化。教师可以实时维护课程信息,学生能通过可视化界面完成选课策略,管理员则拥有全局数据监控能力。实测在3000人并发选课场景下,系统响应时间稳定在1.2秒以内。
2. 技术架构解析
2.1 为什么选择SSM+Django组合?
后端采用SSM(Spring+SpringMVC+MyBatis)框架组合不是偶然。Spring的IOC容器管理着56个核心Bean,包括:
- 选课事务管理器(@Transactional隔离级别配置为REPEATABLE_READ)
- Redis缓存集群客户端(Jedis连接池配置最大200连接)
- 课表冲突检测器(基于时间片重叠算法)
而Django-admin的快速开发特性,让我们用3天就完成了:
python复制class CourseAdmin(admin.ModelAdmin):
list_display = ('course_code', 'teacher', 'capacity')
filter_horizontal = ('conflict_courses',)
actions = ['export_statistics']
这种组合既保证了核心业务的高性能(SSM),又快速实现了管理后台(Django)。
2.2 数据库设计的三个关键点
- 课程-学生多对多关系:通过中间表实现选课记录,包含选课时间、状态等元数据
sql复制CREATE TABLE selection_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id VARCHAR(12) NOT NULL,
course_id VARCHAR(8) NOT NULL,
selection_time DATETIME DEFAULT CURRENT_TIMESTAMP,
status ENUM('PENDING','SUCCESS','DROPPED') NOT NULL,
UNIQUE KEY uk_student_course (student_id, course_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
冲突课程组设计:使用邻接表存储课程冲突关系,应用层通过广度优先搜索检测冲突
-
Redis缓存策略:
- 课程余量使用STRING类型(key: course:capacity:C001)
- 热门课程采用ZSET实现排队(key: course:queue:C001)
3. 核心业务实现
3.1 选课事务的ACID保障
选课操作必须处理三个关键操作:
- 检查课程余量
- 检查冲突课程
- 创建选课记录
我们采用Spring声明式事务管理:
java复制@Transactional(isolation = Isolation.REPEATABLE_READ)
public SelectionResult selectCourse(String studentId, String courseCode) {
// 1. 校验余量(带悲观锁)
Course course = courseMapper.selectForUpdate(courseCode);
if (course.getSelected() >= course.getCapacity()) {
return SelectionResult.fail("课程已满");
}
// 2. 冲突检测
if (conflictDetector.hasConflict(studentId, courseCode)) {
return SelectionResult.fail("存在时间冲突");
}
// 3. 创建记录
SelectionRecord record = new SelectionRecord(studentId, courseCode);
selectionMapper.insert(record);
// 4. 更新余量
courseMapper.incrementSelected(courseCode);
return SelectionResult.success();
}
3.2 高并发场景下的优化方案
压力测试时发现,热门课程(如名师公选课)会出现严重的超卖问题。我们最终采用三级防护:
- 前端限流:选课按钮点击后禁用3秒
- Redis原子计数器:
java复制Long remain = redisTemplate.opsForValue()
.decrement("course:capacity:"+courseCode);
if (remain < 0) {
redisTemplate.opsForValue().increment("course:capacity:"+courseCode);
throw new BusinessException("课程已满");
}
- 数据库最终校验:如前述的selectForUpdate
4. 特色功能实现
4.1 智能推荐算法
基于协同过滤的课程推荐,数据源包括:
- 同专业学生的选课历史
- 相同基础课程的选择路径
- 教师教学评价分数
算法核心:
python复制def recommend_courses(student_id, top_n=5):
# 获取相似学生
similar_students = find_similar_users(student_id)
# 聚合课程评分
course_scores = defaultdict(float)
for s in similar_students:
for course, rating in get_selections(s):
course_scores[course] += rating
# 排除已选课程
selected = get_selected_courses(student_id)
return sorted(
[c for c in course_scores if c not in selected],
key=lambda x: -course_scores[x]
)[:top_n]
4.2 可视化课表生成
使用ECharts实现的三维课表:
- X轴:星期一到星期五
- Y轴:8:00~21:00时间段
- Z轴:教室位置
通过WebSocket实时推送课表变更,响应时间<300ms
5. 踩坑实录
5.1 缓存一致性问题
初期采用"先更新数据库再删缓存"策略,在并发环境下会出现:
- 线程A更新数据库
- 线程B查询旧缓存
- 线程A删除缓存
解决方案:引入Redisson分布式锁,保证操作原子性
5.2 MyBatis批量插入优化
原始方案逐条插入3000条选课记录需要28秒,优化后:
xml复制<insert id="batchInsert" parameterType="list">
INSERT INTO selection_record
(student_id, course_id) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.studentId}, #{item.courseId})
</foreach>
</insert>
批量插入降至1.3秒
6. 部署架构建议
生产环境推荐配置:
- Web层:Nginx+Tomcat集群(至少2节点)
- 服务层:SpringBoot服务独立部署
- 缓存层:Redis哨兵模式(1主2从)
- 数据库:MySQL主从复制+MyCat分片
监控指标重点关注:
- 选课接口的TP99响应时间
- 数据库连接池使用率
- Redis缓存命中率
这套系统在某高校实际运行后,选课季的教务处投诉量下降了76%。最大的收获是:技术方案必须贴合实际业务场景,比如我们发现学生最需要的不是更多功能,而是稳定的服务和清晰的操作指引。