1. 项目背景与核心需求
选课管理系统是高校教务管理中的核心模块之一,传统基于JSP/Servlet或Struts2的选课系统普遍存在响应速度慢、界面交互差、并发处理能力弱等问题。这套基于SpringBoot+Vue的前后端分离架构,正是为了解决这些痛点而生。
我在实际开发中发现,一个合格的选课系统需要满足三个核心需求:
- 高并发处理:选课开放时段的瞬时并发可达数千级别
- 实时数据一致性:避免超选、重复选课等数据冲突
- 移动端适配:学生越来越依赖手机完成选课操作
SpringBoot的自动配置和内置Tomcat提供了开箱即用的高性能后端服务,而Vue的响应式特性和组件化开发则完美适配动态交互需求。二者通过RESTful API对接,形成了典型的现代化Web应用架构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型分析
2.1 后端技术栈
选择SpringBoot而非原生Spring的主要考虑:
java复制// 传统Spring MVC配置示例 vs SpringBoot自动配置
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// 需要手动配置视图解析器、静态资源等
}
// SpringBoot只需application.properties配置
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html
关键组件选型:
- 持久层:MyBatis-Plus(比JPA更灵活,内置通用Mapper)
- 缓存:Redis(选课结果缓存,减轻数据库压力)
- 安全框架:Spring Security(OAuth2 + JWT方案)
- 文件处理:POI-TL(模板引擎生成选课表PDF)
2.2 前端技术栈
Vue生态选型考量:
javascript复制// 典型Vue3组合式API示例
const courseList = ref([])
const loadCourses = async () => {
const res = await axios.get('/api/courses')
courseList.value = res.data.filter(item =>
item.status === 'OPEN'
)
}
核心依赖:
- UI框架:Element Plus(表单、表格组件丰富)
- 状态管理:Pinia(比Vuex更简洁的API)
- 路由:Vue Router(支持动态路由权限)
- 可视化:ECharts(选课数据统计展示)
3. 系统架构设计
3.1 整体架构图
code复制[前端层] Vue3 + Axios
↑↓ HTTP/JSON
[网关层] Nginx反向代理
↑↓
[应用层] SpringBoot + SpringSecurity
↑↓
[服务层] MyBatis-Plus + Redis
↑↓
[数据层] MySQL集群
3.2 数据库设计要点
课程表核心字段设计:
sql复制CREATE TABLE `course` (
`id` bigint NOT NULL AUTO_INCREMENT,
`course_code` varchar(32) NOT NULL COMMENT '课程编号',
`name` varchar(64) NOT NULL,
`credit` tinyint NOT NULL DEFAULT 1,
`max_students` int NOT NULL DEFAULT 100,
`selected` int NOT NULL DEFAULT 0 COMMENT '已选人数',
`teacher_id` bigint NOT NULL,
`time_slot` json DEFAULT NULL COMMENT '上课时间JSON',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`course_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意:
- 使用JSON类型存储灵活的时间安排
- 建立联合索引优化查询性能
- 添加version字段实现乐观锁
4. 核心功能实现
4.1 选课并发控制
采用Redis+Lua脚本实现原子操作:
lua复制-- 选课脚本示例
local key = KEYS[1]
local max = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")
if current >= max then
return 0
else
redis.call('INCR', key)
return 1
end
Java调用逻辑:
java复制public boolean selectCourse(Long courseId) {
String luaScript = "上面Lua脚本内容";
RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList("course:"+courseId),
courseMapper.selectMaxStudents(courseId));
return result == 1;
}
4.2 前后端数据交互
Axios请求封装示例:
javascript复制// src/utils/request.js
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
}
)
5. 关键问题解决方案
5.1 选课冲突检测
时间冲突校验算法:
java复制public boolean checkTimeConflict(List<TimeSlot> existing, TimeSlot newSlot) {
return existing.stream().anyMatch(slot ->
slot.getWeekday() == newSlot.getWeekday() &&
!(slot.getEndTime() <= newSlot.getStartTime() ||
slot.getStartTime() >= newSlot.getEndTime())
);
}
5.2 大数据量导出优化
使用POI-TL模板引擎替代传统POI:
java复制// 配置模板
Configure config = Configure.builder()
.bind("courses", new CoursesPolicy())
.build();
// 渲染数据
Template template = Template.render("template.docx",
new HashMap<String, Object>(){{
put("courses", courseList);
}},
config);
实测性能对比:
| 数据量 | POI耗时 | POI-TL耗时 |
|---|---|---|
| 100条 | 1200ms | 400ms |
| 1000条 | 6500ms | 1800ms |
6. 部署与性能优化
6.1 Docker部署方案
SpringBoot的Dockerfile示例:
dockerfile复制FROM openjdk:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Nginx配置要点:
nginx复制location /api {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 75s;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
6.2 性能调优参数
application.yml关键配置:
yaml复制server:
tomcat:
max-threads: 200
min-spare-threads: 10
max-connections: 1000
accept-count: 100
spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 30000
7. 开发经验与避坑指南
- Vue路由缓存问题:
javascript复制// 解决keep-alive导致表格滚动位置重置
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
- MyBatis-Plus逻辑删除陷阱:
java复制// 需要在实体类明确指定逻辑删除字段
@TableLogic
private Integer deleted;
- SpringBoot跨域配置注意:
java复制// 生产环境应严格限制origin
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("https://yourdomain.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
这套系统在实际部署后经受住了3000+并发选课的考验,平均响应时间保持在800ms以下。最大的收获是认识到良好的架构设计比堆砌功能更重要,特别是在处理高并发场景时,合理的缓存策略和异步处理能带来质的提升
