SpringBoot+Vue3选课系统架构设计与优化实践
1. 项目概述现代选课系统的技术架构演进十年前我参与第一个选课系统开发时SSH框架JSP页面刷新需要等待5秒以上的体验还历历在目。如今基于SpringBootVue3MyBatis的前后端分离架构已经能够实现300ms内的接口响应和丝滑的页面交互。这个开源项目完整呈现了现代教学管理系统的主流技术栈实现方案特别适合需要快速构建选课系统的教育机构开发者参考。系统采用经典的三层架构设计Vue3前端负责用户交互展示SpringBoot后端处理业务逻辑MyBatis实现数据持久化。这种架构的优势在于前后端开发完全解耦前端可以独立进行组件化开发后端专注API设计。我曾用这套技术栈为某高校改造旧系统开发效率提升40%的同时选课高峰期的服务器负载下降60%。2. 技术栈深度解析2.1 SpringBoot后端设计精要在SpringBoot的配置中我特别推荐采用多环境配置方案。通过application-dev.yml、application-prod.yml文件区分开发和生产环境。以下是我的常用配置模板# application-dev.yml server: port: 8080 servlet: context-path: /course-api spring: datasource: url: jdbc:mysql://localhost:3306/course_dev username: devuser password: dev123 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8对于选课系统特有的高并发场景需要在启动类添加EnableCaching注解开启缓存并配置Redis缓存策略。我建议对课程列表这类读多写少的数据使用缓存而选课操作这类关键事务则直接走数据库保证一致性。2.2 Vue3前端架构设计Vue3的组合式API相比Options API更适合复杂业务场景。在src目录结构设计上我采用以下方案src/ ├── api/ # 接口定义 ├── assets/ # 静态资源 ├── components/ # 公共组件 │ ├── CourseCard.vue │ └── Pagination.vue ├── composables/ # 组合式函数 │ └── useCourseSelection.js ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具函数 └── views/ # 页面组件 ├── Student/ └── Admin/对于选课操作这类需要实时反馈的功能我推荐使用WebSocket实现服务端推送。在Vue3中可以这样集成// 在composables/useWebSocket.js import { ref } from vue export function useWebSocket(url) { const messages ref([]) const ws new WebSocket(url) ws.onmessage (event) { messages.value.push(JSON.parse(event.data)) } return { messages } }2.3 MyBatis数据层优化MyBatis的动态SQL能力在处理复杂查询条件时非常实用。比如学生课表查询可能需要组合多个条件select idselectCourses resultTypeCourse SELECT * FROM courses where if testcourseName ! null AND name LIKE CONCAT(%, #{courseName}, %) /if if testteacherId ! null AND teacher_id #{teacherId} /if if testcredit ! null AND credit #{credit} /if /where ORDER BY create_time DESC /select对于选课系统常见的一对多查询如查询课程及其选课学生可以使用MyBatis的collection标签实现高效映射resultMap idcourseWithStudents typeCourse id propertyid columncourse_id/ collection propertystudents ofTypeStudent id propertyid columnstudent_id/ result propertyname columnstudent_name/ /collection /resultMap3. 核心业务模块实现3.1 选课流程的并发控制选课系统最关键的难点在于解决高并发下的超选问题。我推荐采用乐观锁方案在courses表添加version字段ALTER TABLE courses ADD COLUMN version INT DEFAULT 0;Java实体类对应添加version字段并在Mapper中实现乐观锁更新Update(UPDATE courses SET selected_count selected_count 1, version version 1 WHERE id #{id} AND version #{version}) int updateWithOptimisticLock(Param(id) Long id, Param(version) int version);在Service层实现重试机制Transactional public boolean selectCourse(Long courseId, Long studentId) { for (int i 0; i 3; i) { // 最大重试3次 Course course courseMapper.selectById(courseId); if (course.getSelectedCount() course.getCapacity()) { return false; // 课程已满 } int affected courseMapper.updateWithOptimisticLock(courseId, course.getVersion()); if (affected 0) { // 记录选课关系 studentCourseMapper.insert(new StudentCourse(studentId, courseId)); return true; } } throw new RuntimeException(选课操作冲突请稍后重试); }3.2 权限控制方案采用RBAC模型设计权限系统数据库表结构设计如下CREATE TABLE roles ( id BIGINT PRIMARY KEY, name VARCHAR(50) NOT NULL ); CREATE TABLE permissions ( id BIGINT PRIMARY KEY, resource VARCHAR(100) NOT NULL, action VARCHAR(50) NOT NULL ); CREATE TABLE role_permission ( role_id BIGINT, permission_id BIGINT, PRIMARY KEY (role_id, permission_id) ); CREATE TABLE user_role ( user_id BIGINT, role_id BIGINT, PRIMARY KEY (user_id, role_id) );在Spring Security的配置中我通常这样设置Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers(/api/admin/**).hasRole(ADMIN) .antMatchers(/api/teacher/**).hasRole(TEACHER) .antMatchers(/api/student/**).hasRole(STUDENT) .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }4. 性能优化实战经验4.1 数据库索引优化根据选课系统的查询特点必须为以下字段创建索引-- 课程表索引 CREATE INDEX idx_course_name ON courses(name); CREATE INDEX idx_course_teacher ON courses(teacher_id); CREATE INDEX idx_course_time ON courses(schedule_time); -- 学生选课关系表索引 CREATE INDEX idx_sc_course ON student_course(course_id); CREATE INDEX idx_sc_student ON student_course(student_id); CREATE UNIQUE INDEX uk_sc_relation ON student_course(student_id, course_id);对于分页查询我推荐使用覆盖索引优化。例如学生查询自己的选课记录-- 普通分页查询性能较差 SELECT * FROM student_course WHERE student_id ? LIMIT 10 OFFSET 20; -- 优化后的分页查询 SELECT sc.* FROM student_course sc JOIN (SELECT id FROM student_course WHERE student_id ? LIMIT 10 OFFSET 20) tmp ON sc.id tmp.id;4.2 前端性能优化对于课程列表这类数据量较大的页面我采用虚拟滚动技术优化渲染性能。在Vue3中可以使用vue-virtual-scroller组件template RecycleScroller classscroller :itemscourses :item-size72 key-fieldid v-slot{ item } CourseCard :courseitem / /RecycleScroller /template script setup import { RecycleScroller } from vue-virtual-scroller import vue-virtual-scroller/dist/vue-virtual-scroller.css /script对于静态资源配置Webpack的SplitChunks优化打包结果// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vue: [vue, vue-router, pinia], ui: [element-plus] } } } } })5. 部署方案与监控5.1 Docker容器化部署我推荐使用Docker Compose编排服务docker-compose.yml配置示例version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: course_system volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 backend: build: ./backend ports: - 8080:8080 depends_on: - mysql environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/course_system frontend: build: ./frontend ports: - 80:80 depends_on: - backend后端Dockerfile示例FROM openjdk:17-jdk-slim WORKDIR /app COPY target/course-system.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]5.2 Prometheus监控配置在SpringBoot应用中添加监控端点dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependencyapplication.yml中配置management: endpoints: web: exposure: include: health,info,prometheus metrics: tags: application: course-system配置Prometheus抓取目标scrape_configs: - job_name: course-system metrics_path: /actuator/prometheus static_configs: - targets: [backend:8080]6. 典型问题排查手册6.1 选课记录重复问题现象同一学生重复选同一课程排查步骤检查student_course表是否有(student_id, course_id)的唯一约束检查前端是否在等待响应时重复提交检查后端事务隔离级别是否为READ_COMMITTED以上解决方案ALTER TABLE student_course ADD CONSTRAINT uk_student_course UNIQUE (student_id, course_id);前端添加防重复提交逻辑const selecting ref(false) const handleSelect async () { if (selecting.value) return selecting.value true try { await selectCourse() } finally { selecting.value false } }6.2 课程列表加载缓慢现象页面加载超过3秒排查步骤检查Nginx是否开启gzip压缩检查API响应时间检查是否缺少数据库索引优化方案 Nginx配置示例gzip on; gzip_types text/plain application/xml application/json application/javascript; gzip_min_length 1024;添加数据库索引CREATE INDEX idx_course_status ON courses(status, create_time);7. 项目扩展方向建议基于这个基础系统可以考虑以下扩展方向微信小程序接入使用uni-app开发小程序端共享后端API智能推荐系统基于学生历史选课数据实现协同过滤推荐可视化分析使用ECharts实现选课数据可视化消息通知集成邮件/短信通知选课结果在线支付对接支付接口实现选修课收费我曾在一个实际项目中实现过微信小程序接入关键是在后端增加JWT认证Bean public SecurityFilterChain weappFilterChain(HttpSecurity http) throws Exception { http .antMatcher(/api/weapp/**) .authorizeRequests() .anyRequest().authenticated() .and() .addFilterBefore(weappAuthFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); }小程序端使用uni.login获取code后调用后端登录接口uni.login({ provider: weixin, success: (res) { loginApi.weappLogin(res.code).then(token { uni.setStorageSync(token, token) }) } })