选择SpringBoot+Vue+微信小程序的技术组合并非偶然。在高校信息化建设领域,这套技术栈具有显著优势:
后端选择SpringBoot的三大理由:
前端采用Vue+小程序的双端策略:
课程管理系统的核心表关系采用星型结构设计:
sql复制-- 课程主表
CREATE TABLE `course` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`course_name` varchar(100) NOT NULL COMMENT '课程名称',
`teacher_id` bigint(20) NOT NULL COMMENT '授课教师',
`classroom` varchar(50) DEFAULT NULL COMMENT '教室',
`schedule` varchar(255) NOT NULL COMMENT '时间安排JSON',
`capacity` int(11) NOT NULL DEFAULT '50' COMMENT '容量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 选课记录表
CREATE TABLE `selection` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`student_id` bigint(20) NOT NULL,
`course_id` bigint(20) NOT NULL,
`select_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1有效 0退课',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_student_course` (`student_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计要点:
- 课程表schedule字段存储JSON格式的时间数据,如
{"weekday":3,"start":"14:00","end":"15:30"}- 选课记录表建立(student_id,course_id)唯一索引,避免重复选课
- 采用utf8mb4字符集支持emoji等特殊符号
传统的时间冲突检测采用双重循环比对,时间复杂度O(n²)。本系统优化后的检测逻辑:
java复制public boolean checkScheduleConflict(Long studentId, Course newCourse) {
// 获取学生已选课程
List<Course> selectedCourses = courseMapper.selectSelectedCourses(studentId);
// 使用BitMap表示周课时段(每周7天×24小时×4个时段)
BitSet weekSchedule = new BitSet(7 * 24 * 4);
// 填充已选课程时间
for (Course course : selectedCourses) {
Schedule schedule = JSON.parseObject(course.getSchedule(), Schedule.class);
int startSlot = calculateTimeSlot(schedule.getWeekday(), schedule.getStart());
int endSlot = calculateTimeSlot(schedule.getWeekday(), schedule.getEnd());
weekSchedule.set(startSlot, endSlot + 1);
}
// 检测新课程时间冲突
Schedule newSchedule = JSON.parseObject(newCourse.getSchedule(), Schedule.class);
int newStart = calculateTimeSlot(newSchedule.getWeekday(), newSchedule.getStart());
int newEnd = calculateTimeSlot(newSchedule.getWeekday(), newSchedule.getEnd());
return weekSchedule.get(newStart, newEnd + 1).cardinality() > 0;
}
private int calculateTimeSlot(int weekday, String time) {
String[] parts = time.split(":");
int hour = Integer.parseInt(parts[0]);
int minute = Integer.parseInt(parts[1]);
return (weekday-1)*96 + hour*4 + minute/15;
}
该算法将时间离散化为15分钟间隔的时隙,使用BitSet进行位运算检测,性能提升约8倍(实测从120ms降至15ms)。
选课高峰期面临的主要挑战:
我们采用的解决方案:
分布式锁+乐观锁方案:
java复制@Transactional
public SelectionResult selectCourse(Long studentId, Long courseId) {
// 获取分布式锁(Redisson实现)
RLock lock = redissonClient.getLock("course:" + courseId);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 检查选课资格
if (selectionMapper.existsSelection(studentId, courseId)) {
return SelectionResult.fail("不能重复选课");
}
// 乐观锁更新课程名额
int updated = courseMapper.decreaseCapacityWithVersion(
courseId,
courseMapper.selectVersion(courseId)
);
if (updated == 0) {
return SelectionResult.fail("课程已满");
}
// 记录选课
selectionMapper.insert(new Selection(studentId, courseId));
return SelectionResult.success();
}
} finally {
lock.unlock();
}
return SelectionResult.fail("系统繁忙,请重试");
}
性能优化措施:
传统方案每次请求都校验token会导致性能瓶颈。改进后的双token机制:
javascript复制// 小程序端登录逻辑
wx.login({
success: res => {
wx.request({
url: '/api/auth/login',
method: 'POST',
data: {
code: res.code,
studentId: '201910123'
},
success: ({data}) => {
// 存储双token
wx.setStorageSync('access_token', data.accessToken)
wx.setStorageSync('refresh_token', data.refreshToken)
// 设置定时刷新
this.scheduleTokenRefresh(data.expiresIn)
}
})
}
})
// 定时刷新token
scheduleTokenRefresh(expiresIn) {
setTimeout(() => {
wx.request({
url: '/api/auth/refresh',
method: 'POST',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('refresh_token')
},
success: ({data}) => {
wx.setStorageSync('access_token', data.accessToken)
this.scheduleTokenRefresh(data.expiresIn)
}
})
}, (expiresIn - 300) * 1000) // 提前5分钟刷新
}
安全增强措施:
首屏加载优化方案:
json复制{
"subpackages": [{
"root": "packageCourse",
"pages": [
"pages/course/list",
"pages/course/detail"
]
}]
}
javascript复制// app.js中预加载必要数据
App({
onLaunch() {
this.preloadData()
},
preloadData() {
wx.request({
url: '/api/system/preload',
success: ({data}) => {
wx.setStorageSync('preloadData', data)
}
})
}
})
使用JMeter模拟3000并发用户测试结果:
| 测试场景 | 平均响应时间 | 错误率 | TPS |
|---|---|---|---|
| 课程查询 | 128ms | 0% | 2850 |
| 选课操作 | 236ms | 1.2% | 920 |
| 退课操作 | 198ms | 0.8% | 1100 |
性能瓶颈解决方案:
典型问题1:选课状态不同步
典型问题2:小程序白屏
典型问题3:高并发时数据库连接耗尽
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 100
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
使用Docker Compose编排方案:
yaml复制version: '3.8'
services:
app:
image: registry.cn-hangzhou.aliyuncs.com/edu/select-course:1.0.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- REDIS_HOST=redis
depends_on:
- redis
- mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: course_db
volumes:
- mysql_data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
redis_data:
mysql_data:
运维关键点:
标准化发布checklist:
基于学生历史选课数据的推荐算法架构:
code复制数据层
├─ 选课记录
├─ 成绩数据
├─ 课程评价
特征工程
├─ 课程相似度计算
├─ 学生兴趣画像
├─ 热度衰减因子
算法层
├─ 协同过滤
├─ 内容相似度
├─ 混合推荐
API层
└─ 推荐结果缓存
单体架构拆分方案:
采用Spring Cloud Alibaba技术栈:
我在实际开发中发现,微信小程序在校园场景下的适配需要特别注意三点:首先是权限控制要严格,不同角色(学生/教师/管理员)的界面差异很大;其次要考虑校园网环境的不稳定性,需要做好本地数据缓存;最后是选课这类关键操作必须设计完善的防重放机制。