1. 项目概述与背景
作为一名经历过多次选课系统崩溃的老学长,我深知一个稳定高效的选课系统对学生和教务人员的重要性。这次基于Spring Boot的学生选课管理系统开发,正是为了解决传统选课方式中存在的诸多痛点。
传统选课系统常见的问题包括:选课高峰期服务器崩溃、操作界面复杂、选课结果反馈延迟等。我在大二时就经历过凌晨三点蹲守选课,结果系统卡死导致心仪课程被抢光的惨痛教训。而这次设计的系统,通过Spring Boot的轻量级特性和MySQL的高效数据管理,能够支持高并发选课场景,确保系统稳定运行。
系统采用B/S架构,分为管理员、教师和学生三个角色。管理员负责基础数据维护,教师管理课程和成绩,学生则进行选课操作。这种权限分离的设计既保证了系统安全,又符合实际教学管理流程。
2. 系统设计与技术选型
2.1 技术栈选择
在技术选型上,我们经过多方考量最终确定了以下技术组合:
- 后端框架:Spring Boot 2.5.6
- 数据库:MySQL 8.0
- 前端技术:Thymeleaf + Bootstrap
- 开发工具:IntelliJ IDEA 2021.2
- 版本控制:Git
选择Spring Boot的主要原因在于其"约定优于配置"的理念,可以快速搭建项目骨架。相比传统的SSM框架,Spring Boot内置Tomcat服务器,简化了部署流程,特别适合课程设计这类小型项目开发。
MySQL作为关系型数据库,在数据一致性和事务处理方面表现出色。我们使用8.0版本主要是看中了其性能提升和JSON支持等新特性,虽然5.7版本也能满足需求,但考虑到长期维护还是选择了更新的版本。
2.2 系统架构设计
系统采用经典的三层架构:
- 表现层:负责接收用户请求和返回响应
- 业务逻辑层:处理核心业务规则和数据校验
- 数据访问层:与数据库交互,执行CRUD操作
这种分层设计使得各层职责明确,便于后期维护和功能扩展。例如当需要添加新的选课规则时,只需在业务层进行修改,不会影响到其他层次。
3. 数据库设计与实现
3.1 数据库表结构
系统主要包含以下核心表:
-
学生表(student)
sql复制CREATE TABLE `student` ( `id` int NOT NULL AUTO_INCREMENT, `student_id` varchar(20) NOT NULL COMMENT '学号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` char(1) DEFAULT NULL COMMENT '性别', `major` varchar(100) DEFAULT NULL COMMENT '专业', `class_name` varchar(50) DEFAULT NULL COMMENT '班级', `phone` varchar(20) DEFAULT NULL COMMENT '手机', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `password` varchar(100) NOT NULL COMMENT '密码', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', PRIMARY KEY (`id`), UNIQUE KEY `idx_student_id` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -
课程表(course)
sql复制CREATE TABLE `course` ( `id` int NOT NULL AUTO_INCREMENT, `course_code` varchar(20) NOT NULL COMMENT '课程编号', `name` varchar(100) NOT NULL COMMENT '课程名称', `type` varchar(50) DEFAULT NULL COMMENT '课程类型', `credit` int DEFAULT NULL COMMENT '学分', `hours` int DEFAULT NULL COMMENT '课时', `location` varchar(100) DEFAULT NULL COMMENT '上课地点', `schedule` varchar(255) DEFAULT NULL COMMENT '上课时间', `teacher_id` int DEFAULT NULL COMMENT '授课教师', `max_quota` int DEFAULT NULL COMMENT '最大选课人数', `current_quota` int DEFAULT '0' COMMENT '当前选课人数', PRIMARY KEY (`id`), UNIQUE KEY `idx_course_code` (`course_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -
选课记录表(selection)
sql复制CREATE TABLE `selection` ( `id` int NOT NULL AUTO_INCREMENT, `student_id` int NOT NULL COMMENT '学生ID', `course_id` int NOT NULL COMMENT '课程ID', `select_time` datetime NOT NULL COMMENT '选课时间', `status` tinyint DEFAULT '1' COMMENT '状态:1-已选 2-已取消', `score` decimal(5,2) DEFAULT NULL COMMENT '成绩', PRIMARY KEY (`id`), UNIQUE KEY `idx_student_course` (`student_id`,`course_id`), KEY `idx_course` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库优化措施
为了提高系统性能,我们采取了以下优化措施:
- 索引优化:在经常查询的字段上建立索引,如学号、课程编号等
- 连接池配置:使用HikariCP连接池管理数据库连接
yaml复制spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 - 查询优化:避免使用SELECT *,只查询需要的字段
- 事务管理:对关键操作如选课、取消选课使用@Transactional注解确保数据一致性
4. 核心功能实现
4.1 选课功能实现
选课是系统的核心功能,需要考虑并发控制和业务规则校验。以下是选课服务的主要实现逻辑:
java复制@Service
@Transactional
public class CourseSelectionServiceImpl implements CourseSelectionService {
@Autowired
private CourseMapper courseMapper;
@Autowired
private SelectionMapper selectionMapper;
@Override
public synchronized SelectionResult selectCourse(Integer studentId, Integer courseId) {
// 1. 检查课程是否存在且可选
Course course = courseMapper.selectById(courseId);
if (course == null) {
return SelectionResult.fail("课程不存在");
}
// 2. 检查是否已选该课程
if (selectionMapper.existsSelection(studentId, courseId)) {
return SelectionResult.fail("已选过该课程");
}
// 3. 检查课程容量
if (course.getCurrentQuota() >= course.getMaxQuota()) {
return SelectionResult.fail("课程已满");
}
// 4. 创建选课记录
Selection selection = new Selection();
selection.setStudentId(studentId);
selection.setCourseId(courseId);
selection.setSelectTime(new Date());
selection.setStatus(SelectionStatus.SELECTED);
selectionMapper.insert(selection);
// 5. 更新课程当前选课人数
courseMapper.incrementCurrentQuota(courseId);
return SelectionResult.success("选课成功");
}
}
关键点说明:
- 使用synchronized关键字保证选课操作的原子性,防止超选
- 采用乐观锁机制更新课程人数,避免并发问题
- 返回详细的选课结果,方便前端展示
4.2 成绩管理功能
成绩管理模块需要处理成绩录入、修改和查询功能。考虑到成绩的敏感性,我们实现了严格的操作日志记录:
java复制@RestController
@RequestMapping("/api/score")
public class ScoreController {
@Autowired
private ScoreService scoreService;
@PostMapping("/update")
public Result updateScore(@RequestBody ScoreUpdateDTO dto,
HttpServletRequest request) {
// 获取操作人信息
String operator = getCurrentUser(request);
try {
scoreService.updateScore(dto, operator);
return Result.success("成绩更新成功");
} catch (Exception e) {
return Result.fail(e.getMessage());
}
}
@GetMapping("/log/{studentId}")
public Result getScoreLogs(@PathVariable Integer studentId) {
return Result.success(scoreService.getScoreLogs(studentId));
}
}
5. 系统安全与性能优化
5.1 安全措施
-
认证与授权:使用Spring Security实现基于角色的访问控制
java复制@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/teacher/**").hasRole("TEACHER") .antMatchers("/student/**").hasRole("STUDENT") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } } -
密码加密:使用BCryptPasswordEncoder对密码进行加密存储
java复制@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -
XSS防护:使用HtmlUtils对用户输入进行转义处理
java复制public static String cleanXSS(String value) { if (value == null) { return null; } return HtmlUtils.htmlEscape(value); }
5.2 性能优化
-
缓存策略:对课程列表等高频访问数据使用Redis缓存
java复制@Cacheable(value = "courses", key = "#type") public List<CourseVO> getCoursesByType(String type) { return courseMapper.selectByType(type); } -
异步处理:使用@Async注解处理非关键路径操作
java复制@Async public void sendSelectionNotification(Selection selection) { // 发送选课成功通知邮件 emailService.send(selection.getStudent().getEmail(), "选课成功通知", "您已成功选择课程:" + selection.getCourse().getName()); } -
数据库读写分离:配置多数据源,将读操作路由到从库
java复制@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource routingDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slave", slaveDataSource()); RoutingDataSource routingDataSource = new RoutingDataSource(); routingDataSource.setTargetDataSources(targetDataSources); routingDataSource.setDefaultTargetDataSource(masterDataSource()); return routingDataSource; } }
6. 系统测试与部署
6.1 测试策略
我们采用分层测试策略确保系统质量:
-
单元测试:使用JUnit + Mockito测试各Service方法
java复制@ExtendWith(MockitoExtension.class) class CourseSelectionServiceTest { @Mock private CourseMapper courseMapper; @Mock private SelectionMapper selectionMapper; @InjectMocks private CourseSelectionServiceImpl courseSelectionService; @Test void selectCourseSuccess() { // 准备测试数据 Course course = new Course(); course.setId(1); course.setMaxQuota(100); course.setCurrentQuota(99); when(courseMapper.selectById(1)).thenReturn(course); when(selectionMapper.existsSelection(1, 1)).thenReturn(false); // 执行测试 SelectionResult result = courseSelectionService.selectCourse(1, 1); // 验证结果 assertTrue(result.isSuccess()); verify(selectionMapper).insert(any()); verify(courseMapper).incrementCurrentQuota(1); } } -
集成测试:使用TestContainers测试数据库交互
-
压力测试:使用JMeter模拟高并发选课场景
6.2 部署方案
系统支持多种部署方式:
-
传统部署:
bash复制# 打包 mvn clean package # 运行 java -jar target/selection-system-1.0.0.jar -
Docker部署:
dockerfile复制FROM openjdk:11-jre-slim COPY target/selection-system-1.0.0.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"] -
Kubernetes部署:编写Deployment和Service配置文件
7. 常见问题与解决方案
在实际开发和测试过程中,我们遇到了以下典型问题及解决方案:
-
选课超卖问题
- 现象:高并发下课程选课人数超过限额
- 解决方案:
- 数据库层面添加乐观锁
- 应用层使用synchronized或分布式锁
- 前端限制重复提交
-
成绩修改冲突
- 现象:多位教师同时修改同一学生成绩
- 解决方案:
- 添加版本号字段实现乐观锁
- 记录详细的操作日志
- 提供冲突提示和合并功能
-
系统响应缓慢
- 现象:选课高峰期系统响应变慢
- 优化措施:
- 引入Redis缓存热门课程数据
- 对数据库查询添加适当索引
- 使用CDN加速静态资源加载
-
跨学期课程处理
- 现象:课程跨多个学期时数据混乱
- 解决方案:
- 在课程表中添加学期字段
- 修改查询条件包含学期筛选
- 提供学期切换功能
8. 项目总结与扩展方向
经过这个项目的开发,我深刻体会到Spring Boot在快速开发中的优势,以及良好系统设计的重要性。以下几点是特别值得分享的经验:
- 接口设计先行:在编码前先定义好API接口,可以避免后期大量修改
- 日志记录全面:关键操作都要记录日志,便于问题排查
- 测试覆盖全面:不要忽视测试,特别是边界条件测试
- 文档及时更新:代码变更时同步更新文档,避免文档滞后
对于未来的扩展方向,可以考虑:
- 移动端适配:开发微信小程序或APP版本
- 智能推荐:基于学生历史选课记录推荐相关课程
- 可视化分析:对选课数据进行可视化展示和分析
- 微服务改造:将系统拆分为课程服务、选课服务等微服务
这个项目从需求分析到最终实现历时两个月,期间遇到了各种挑战,但也收获颇丰。特别是在处理高并发选课场景时,通过不断优化和测试,最终实现了稳定可靠的选课功能。希望这个项目经验对正在开发类似系统的同学有所帮助。