基于Spring Boot + Vue的学生网上选课系统实现方案
一、系统架构
graph LR
A[前端Vue.js] --> B[后端Spring Boot]
B --> C[MySQL数据库]
B --> D[Redis缓存]
A --> E[Element UI组件库]
二、技术栈
模块 | 技术选型 |
---|---|
前端框架 | Vue 3 + Vue Router + Pinia |
UI组件库 | Element Plus |
HTTP客户端 | Axios |
后端框架 | Spring Boot 3.x |
安全框架 | Spring Security + JWT |
ORM框架 | MyBatis-Plus |
数据库 | MySQL 8.0 |
缓存 | Redis |
消息队列 | RabbitMQ(选课高峰期削峰) |
部署 | Docker + Nginx |
三、核心功能模块
- 用户管理
- 学生/教师/管理员角色
- JWT身份认证
- 密码加密存储(BCrypt)
- 课程管理
// 课程实体类示例
@Data
public class Course {
private Long id;
private String courseCode; // 课程编号
private String name; // 课程名称
private Integer credit; // 学分
private Integer capacity; // 容量
private Integer selected; // 已选人数
private Long teacherId; // 授课教师
private String timeSlot; // 时间段
}
- 选课管理
- 课程查询(分页+过滤)
- 选课冲突检测(时间/先修课程)
- 选课队列(RabbitMQ异步处理)
- 退课功能
- 成绩管理
- 教师录入成绩
- 学生成绩查询
- GPA自动计算
- 系统管理
- 课程时间设置
- 选课开关控制
- 系统公告发布
四、数据库设计
erDiagram
USER ||--o{ STUDENT_COURSE : "1:N"
COURSE ||--o{ STUDENT_COURSE : "1:N"
TEACHER ||--o{ COURSE : "1:N"
USER {
bigint id PK
varchar username
varchar password
varchar role
}
STUDENT {
bigint id PK
varchar student_id
varchar name
}
TEACHER {
bigint id PK
varchar teacher_id
varchar name
}
COURSE {
bigint id PK
varchar code
varchar name
int credit
int capacity
}
STUDENT_COURSE {
bigint id PK
bigint student_id FK
bigint course_id FK
float grade
}
五、关键接口设计
- 选课接口
@PostMapping("/enroll")
@PreAuthorize("hasRole('STUDENT')")
public Result enrollCourse(@RequestParam Long courseId) {
// 1. 检查选课时间是否开放
// 2. 检查课程容量
// 3. 检查时间冲突
// 4. 发送选课请求到RabbitMQ队列
return Result.success("选课请求已接收");
}
- 课程查询接口
@GetMapping("/courses")
public PageResult<CourseVO> getCourses(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 使用MyBatis-Plus分页查询
Page<Course> pageInfo = new Page<>(page, size);
LambdaQueryWrapper<Course> wrapper = Wrappers.lambdaQuery();
wrapper.like(StringUtils.isNotBlank(keyword), Course::getName, keyword);
return courseService.pageCourses(pageInfo, wrapper);
}
六、前端核心实现
- 选课页面组件
<template>
<el-table :data="courses">
<el-table-column prop="name" label="课程名称"/>
<el-table-column prop="teacher" label="教师"/>
<el-table-column label="操作">
<template #default="{row}">
<el-button
@click="enroll(row.id)"
:disabled="row.selected >= row.capacity">
{{ row.selected >= row.capacity ? '已满' : '选课' }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination @current-change="loadCourses" />
</template>
<script setup>
import { ref } from 'vue';
import { enrollCourse, getCourses } from '@/api/course';
const courses = ref([]);
const loadCourses = async (page = 1) => {
const res = await getCourses({ page });
courses.value = res.data;
};
const enroll = async (courseId) => {
try {
await enrollCourse(courseId);
ElMessage.success('选课成功');
} catch (err) {
ElMessage.error(err.message);
}
};
</script>
七、安全设计
- JWT认证流程
sequenceDiagram
前端->>后端: 登录请求(用户名/密码)
后端->>后端: 验证凭证
后端->>前端: 返回JWT令牌
前端->>后端: 携带令牌请求API
后端->>后端: 验证令牌有效性
后端->>前端: 返回请求数据
- 权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/teacher/**").hasRole("TEACHER")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
八、性能优化
- 使用Redis缓存:
- 课程信息缓存
- 选课结果缓存
- 系统配置缓存
- 选课高峰期处理:
- RabbitMQ队列削峰
- 数据库乐观锁更新
UPDATE course SET selected = selected + 1
WHERE id = #{courseId} AND selected < capacity
- 前端优化:
- 路由懒加载
- API请求节流
- 虚拟滚动长列表
九、部署方案
- 容器化部署
# Spring Boot Dockerfile
FROM openjdk:17
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
- Nginx配置
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
}
}
十、扩展功能
- 冲突检测算法:
function checkTimeConflict(existing, newCourse) {
const existingSlots = existing.map(c => c.timeSlot.split('-'));
const newSlot = newCourse.timeSlot.split('-');
return existingSlots.some(([start1, end1]) => {
return (newSlot[0] >= start1 && newSlot[0] < end1) ||
(newSlot[1] > start1 && newSlot[1] <= end1);
});
}
- 数据看板:
- ECharts可视化选课数据
- 课程热度排行榜
- 学生选课分布图
- 消息推送:
- WebSocket实时通知选课结果
- 邮件发送选课成功提醒
系统亮点:
- 采用分布式锁保证选课操作的原子性
- 基于时间窗口的限流机制(Redis + Lua)
- 自动化冲突检测算法
- 响应式前端设计兼容移动端