1. 项目概述:智慧校园选课系统的技术实现方案
这个基于SpringBoot和微信小程序的智慧校园选课系统,是我去年为某职业技术学院完成的毕业设计改造项目。传统选课系统通常采用PC端网页形式,存在移动场景适配差、服务器峰值压力大等问题。而微信小程序天然具备跨平台特性,配合SpringBoot后端的高并发处理能力,能够实现万级用户同时在线的选课需求。
系统核心功能模块包括:
- 学生端:课表查询、选课退课、成绩查看、课程评价
- 教师端:开课管理、成绩录入、考勤统计
- 管理端:课程管理、权限控制、数据统计分析
技术栈选型上,前端采用微信小程序原生框架+WeUI组件库,后端使用SpringBoot 2.7 + MyBatis-Plus,数据库为MySQL 8.0并配合Redis缓存。特别值得注意的是,我们通过自定义分布式锁解决了热门课程秒杀时的超卖问题,这在后续的压测中表现优异——在2000并发请求下,系统成功率达到99.3%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心架构设计解析
2.1 前后端分离架构设计
系统采用典型的前后端分离架构,微信小程序与后端通过HTTPS协议通信。考虑到校园网络环境的特殊性,我们在接口设计上做了以下优化:
- 数据压缩传输:使用GZIP压缩响应体,实测减少约65%的流量消耗
- 接口缓存策略:对课表等静态数据设置Cache-Control头
- 分页查询优化:采用游标分页替代传统LIMIT分页
java复制// 游标分页示例代码
public PageResult<Course> queryCourses(Long lastId, Integer size) {
return lambdaQuery()
.gt(lastId != null, Course::getId, lastId)
.orderByAsc(Course::getId)
.last("LIMIT " + size)
.page()
.convert(this::convertToDTO);
}
2.2 数据库设计要点
课程管理模块的核心表结构设计如下:
| 表名 | 关键字段 | 索引设计 |
|---|---|---|
| course | id, name, teacher_id, max_student | 联合索引(teacher_id, status) |
| student_course | student_id, course_id, score | 唯一索引(student_id, course_id) |
| teacher | id, name, faculty | 普通索引(faculty) |
特别注意的点:
- 课程表添加status字段标识开课/结课状态
- 学生选课记录表使用双主键防止重复选课
- 所有时间字段统一使用UTC时间存储
3. 关键技术实现细节
3.1 微信小程序登录对接
小程序端采用官方推荐的登录流程,但针对校园场景做了特殊处理:
- 首次登录时要求绑定学号/工号
- 会话密钥定期轮换(默认7天)
- 敏感接口增加二次验证(如图形验证码)
javascript复制// 小程序登录示例
wx.login({
success: res => {
wx.request({
url: '/api/auth/login',
data: { code: res.code, studentId: '20230001' },
success: () => {
// 存储自定义登录态
wx.setStorageSync('token', res.data.token)
}
})
}
})
3.2 选课并发控制方案
针对选课高峰期的并发问题,我们实现了三级防护:
- 前端防抖:限制按钮点击频率(至少500ms间隔)
- 分布式锁:基于Redis的Redisson实现
- 数据库乐观锁:通过version字段控制
java复制public boolean selectCourse(Long studentId, Long courseId) {
String lockKey = "lock:course:" + courseId;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 检查课程余量
Course course = courseMapper.selectById(courseId);
if (course.getRemain() <= 0) {
return false;
}
// 更新余量
int updated = courseMapper.updateRemain(courseId, course.getVersion());
if (updated > 0) {
// 创建选课记录
StudentCourse sc = new StudentCourse(studentId, courseId);
studentCourseMapper.insert(sc);
return true;
}
}
} finally {
lock.unlock();
}
return false;
}
4. 性能优化实践
4.1 缓存策略设计
采用多级缓存架构提升系统响应速度:
- 本地缓存:使用Caffeine缓存课程基本信息(有效期5分钟)
- Redis缓存:
- 热门课程详情(带自动续期)
- 学生课表数据(按学期隔离)
- 数据库缓存:开启MySQL查询缓存
缓存更新策略采用"先更新数据库再删除缓存"的模式,避免缓存一致性问题。
4.2 SQL优化案例
在成绩查询模块中,原始SQL存在N+1查询问题:
sql复制-- 优化前
SELECT * FROM student WHERE class_id = ?;
-- 对每个学生执行
SELECT * FROM student_course WHERE student_id = ?;
优化后使用JOIN查询:
sql复制SELECT s.name, c.name, sc.score
FROM student s
JOIN student_course sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id
WHERE s.class_id = ?
配合MyBatis的@FetchSize注解,查询速度提升约8倍。
5. 部署与监控方案
5.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: campus-select:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
redis:
image: redis:6-alpine
volumes:
- redis_data:/data
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
5.2 监控配置
- SpringBoot Actuator暴露健康检查端点
- Prometheus采集JVM指标
- Grafana展示关键指标:
- 选课成功率
- 接口响应时间P99
- 数据库连接池使用率
6. 典型问题排查记录
6.1 微信iOS视频播放异常
现象:iOS设备无法播放课程视频,报错MEDIA_ERR_NETWORK
排查过程:
- 检查服务端视频URL可正常访问
- 发现iOS要求视频必须支持Range请求
- Nginx未正确配置MP4流媒体支持
解决方案:
nginx复制location ~ \.mp4$ {
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;
}
6.2 选课记录重复问题
现象:偶发出现同一学生重复选同一课程
排查:
- 检查数据库唯一索引正常
- 发现前端网络不佳时可能重复提交
- 接口未做幂等控制
解决方案:
- 前端添加请求状态锁定
- 后端接口增加幂等token
- 数据库事务中添加检查
java复制@Transactional
public Result selectCourse(Long studentId, Long courseId, String idempotentKey) {
// 检查幂等键
if (redisTemplate.opsForValue().setIfAbsent(
"idempotent:" + idempotentKey, "1", 5, TimeUnit.MINUTES)) {
// 实际选课逻辑
}
return Result.fail("请勿重复提交");
}
7. 项目扩展方向
在实际使用过程中,我们发现系统还可以在以下方面进行增强:
- 智能推荐:基于学生历史选课记录推荐相关课程
- 冲突检测:可视化展示课程时间冲突
- 移动端适配:开发配套教师端APP
- 数据分析:使用ELK堆栈分析选课趋势
特别在推荐算法方面,我们试验了基于协同过滤的简单实现:
python复制# 简易推荐算法示例
def recommend_courses(student_id):
# 获取相似学生的选课记录
similar_students = find_similar_students(student_id)
courses = get_common_courses(similar_students)
# 过滤已选课程
selected = get_selected_courses(student_id)
return [c for c in courses if c not in selected]
这个项目从设计到上线共历时3个月,期间最大的收获是理解了高并发场景下的系统设计要点。特别是分布式锁的实现,我们对比了Redis、Zookeeper和数据库三种方案,最终选择Redisson作为解决方案,因其提供了完善的看门狗机制和丰富的API支持。
