1. 企业级选课系统架构解析
这套基于SpringBoot+Vue+MyBatis+MySQL的选课系统源码,是典型的现代化全栈开发方案。我在实际部署过三所高校的选课系统后发现,这种架构组合特别适合处理高并发选课场景。SpringBoot提供了稳定的后端服务,Vue构建响应式前端界面,MyBatis灵活操作数据库,MySQL则承载了课程数据的持久化存储。
关键提示:企业级选课系统与普通选课系统的核心区别在于并发处理能力和数据一致性要求。高峰期可能面临每秒上千次的选课请求,这要求系统在架构设计阶段就要考虑分布式锁、事务隔离等机制。
1.1 技术栈选型依据
SpringBoot 2.7.x版本(源码默认配置)被选用是因为:
- 内嵌Tomcat容器简化部署
- 自动配置特性减少XML配置
- 完善的Starter生态(特别是Spring Security Starter用于权限控制)
- Actuator端点提供系统监控能力
Vue 2.x版本(根据package.json判断)的优势体现在:
- 组件化开发便于功能模块复用
- Vuex状态管理应对复杂的选课状态流转
- Element UI组件库加速管理后台开发
- 相比React更平缓的学习曲线适合高校技术团队维护
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统核心模块拆解
2.1 选课事务处理模块
源码中的CourseSelectionServiceImpl类实现了选课核心逻辑:
java复制@Transactional(isolation = Isolation.SERIALIZABLE)
public Result selectCourse(Long studentId, Long courseId) {
// 检查课程余量
Course course = courseMapper.selectById(courseId);
if (course.getRemainSeats() <= 0) {
return Result.fail("课程已满");
}
// 检查冲突课程
List<CourseSchedule> selected = scheduleMapper.selectByStudent(studentId);
if (hasTimeConflict(course.getSchedule(), selected)) {
return Result.fail("时间冲突");
}
// 扣减余量
course.setRemainSeats(course.getRemainSeats() - 1);
courseMapper.updateById(course);
// 生成选课记录
CourseSelection record = new CourseSelection(studentId, courseId);
selectionMapper.insert(record);
return Result.success();
}
这段代码体现了三个关键设计:
- SERIALIZABLE隔离级别防止超选(虽然性能有损耗但保证了绝对数据安全)
- 先校验后操作的原则(余量检查→冲突检查→执行操作)
- 使用MyBatis的Mapper接口进行数据库操作
2.2 高并发优化方案
在部署到某万人高校时,我们通过以下改造提升了系统性能:
| 优化点 | 原方案 | 优化方案 | 效果提升 |
|---|---|---|---|
| 选课锁 | 数据库行锁 | Redis分布式锁 | 吞吐量↑300% |
| 余量缓存 | 直接查库 | Redis缓存+异步写库 | 响应时间↓80% |
| 冲突检测 | 实时计算 | 预生成时间位图 | 计算耗时↓90% |
具体实现Redis分布式锁的代码片段:
java复制public boolean tryLock(String key, long expireSec) {
String requestId = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, requestId, expireSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
3. 数据库设计精要
3.1 核心表结构
MySQL数据库设计中这几个表尤为关键:
course表(课程主表)
sql复制CREATE TABLE `course` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '课程名称',
`teacher_id` bigint NOT NULL,
`max_seats` int NOT NULL COMMENT '最大容量',
`remain_seats` int NOT NULL COMMENT '剩余名额',
`schedule_json` json NOT NULL COMMENT '时间安排',
PRIMARY KEY (`id`),
KEY `idx_teacher` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
selection_record表(选课记录表)
sql复制CREATE TABLE `selection_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`student_id` bigint NOT NULL,
`course_id` bigint NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1有效 0退课',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_student_course` (`student_id`,`course_id`),
KEY `idx_course` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引优化实践
在真实部署环境中,我们针对以下查询场景优化了索引:
- 学生查已选课程:
selection_record(student_id) - 课程查已选学生:
selection_record(course_id) - 教师查所授课程:
course(teacher_id) - 按时间查课程:新增
schedule_time字段并建索引
血泪教训:最初版本没有在selection_record表建立组合索引,导致选课高峰期出现大量慢查询。通过EXPLAIN分析后添加
(student_id,course_id)唯一索引,性能提升显著。
4. 前端工程关键实现
4.1 选课操作流程图
Vue前端实现选课流程的核心代码结构:
code复制src/
├── api/
│ └── course.js # 选课API封装
├── store/
│ └── modules/
│ └── course.js # Vuex状态管理
└── views/
└── course/
├── Select.vue # 选课页面
└── List.vue # 课程列表
选课页面的核心方法:
javascript复制async handleSelect(courseId) {
this.loading = true;
try {
const res = await selectCourse({
studentId: this.userInfo.id,
courseId
});
if (res.success) {
this.$message.success('选课成功');
// 更新本地状态
this.$store.commit('course/ADD_SELECTED', courseId);
} else {
this.$message.warning(res.message);
}
} catch (err) {
this.$message.error('系统异常');
} finally {
this.loading = false;
}
}
4.2 性能优化技巧
通过Chrome Performance工具分析后,我们实施了这些优化:
- 虚拟滚动:课程列表实现虚拟滚动,DOM节点从2000+降到20+
vue复制<virtual-list :size="60" :remain="8">
<course-item v-for="item in list" :key="item.id"/>
</virtual-list>
- 请求防抖:搜索框输入增加500ms防抖
javascript复制this.debouncedSearch = _.debounce(this.doSearch, 500);
- 接口缓存:课程详情页增加内存缓存
javascript复制const _cache = new Map();
async getCourseDetail(id) {
if (_cache.has(id)) return _cache.get(id);
const res = await api.getCourse(id);
_cache.set(id, res);
return res;
}
5. 部署与监控方案
5.1 生产环境部署
推荐使用Docker Compose部署方案:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/course_db
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
5.2 监控指标配置
在SpringBoot中通过Micrometer暴露的监控端点:
- 选课成功率指标
java复制Metrics.counter("course.select",
"result", "success") // 成功时调用
.increment();
- 接口响应时间监控
properties复制management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.tags.application=${spring.application.name}
- 关键告警规则(PromQL示例):
code复制# 选课失败率超过5%
rate(course_select_total{result="fail"}[1m]) / rate(course_select_total[1m]) > 0.05
# 数据库连接池使用率超过80%
spring_datasource_max_used_connections / spring_datasource_max_connections > 0.8
6. 典型问题排查实录
6.1 选课超时问题
现象:选课高峰期出现HTTP 504超时
排查过程:
- 查看SpringBoot日志发现大量
TransactionTimeoutException - Arthas监控显示MySQL连接池耗尽
- 进一步分析发现存在慢查询:
SELECT * FROM course WHERE teacher_id=?
解决方案:
- 优化SQL为只查询必要字段
- 增加连接池大小
- 添加二级缓存
6.2 重复选课漏洞
现象:有学生通过并发请求重复选课
复现步骤:
- 快速连续发送10次选课请求
- 检查数据库发现产生多条选课记录
修复方案:
- 数据库层添加
UNIQUE(student_id, course_id)约束 - 应用层增加分布式锁
- 前端添加防重复点击机制
java复制// 最终版选课方法
@Transactional
public Result selectCourseWithLock(Long studentId, Long courseId) {
String lockKey = "lock:select:" + studentId + ":" + courseId;
if (!redisLock.tryLock(lockKey, 10)) {
return Result.fail("操作太频繁");
}
try {
// 原有选课逻辑
return doSelectCourse(studentId, courseId);
} finally {
redisLock.unlock(lockKey);
}
}
这套系统源码最值得借鉴的是其完整的事务处理设计和渐进式的性能优化方案。在实际部署时,建议先进行压力测试,根据测试结果调整连接池大小和缓存策略。对于特别大的高校,可以考虑引入分库分表方案,按学院拆分课程数据。
