1. 项目背景与需求分析
通识教育选课系统是高校教务管理中的重要组成部分。记得十年前我刚参加工作那会儿,学校还在使用纸质选课表,每到选课季,教务处门口总是排着长队,老师们加班加点统计选课数据,经常出现课程名额超限、数据录入错误等问题。这种传统管理方式效率低下,错误率高,已经无法满足现代高校的教学管理需求。
基于Java Web的通识教育选课系统正是为了解决这些问题而设计的。系统需要实现以下核心功能:
-
学生端功能:
- 课程查询与选课
- 已选课程管理
- 个人课表查看
- 成绩查询
-
教师端功能:
- 课程信息维护
- 学生名单管理
- 成绩录入
- 教学评价查看
-
管理员端功能:
- 用户管理(学生/教师账号)
- 课程信息管理
- 选课规则设置
- 系统数据统计
提示:在设计初期,我们调研了多所高校的选课流程,发现80%的选课冲突都发生在通识教育课程上,因此系统需要特别关注选课冲突检测和自动排课算法的设计。
2. 技术选型与架构设计
2.1 技术栈选择
经过多次技术论证,我们最终确定了以下技术方案:
前端技术:
- JSP(JavaServer Pages):作为视图层技术,适合快速开发动态网页
- Bootstrap:响应式前端框架,确保系统在PC和移动端都能良好显示
- jQuery:简化DOM操作和AJAX请求
后端技术:
- Java 8:成熟稳定的开发语言,拥有丰富的类库支持
- SSM框架组合:
- Spring:控制反转和面向切面编程
- Spring MVC:模型-视图-控制器架构
- MyBatis:轻量级ORM框架
- MySQL 5.7:关系型数据库,存储系统核心数据
开发工具:
- Eclipse/IntelliJ IDEA:Java开发IDE
- Maven:项目构建和依赖管理
- Git:版本控制
2.2 系统架构设计
系统采用经典的三层B/S架构:
code复制┌───────────────────────────────────────┐
│ 客户端浏览器 │
└───────────────────────────────────────┘
▲
│ HTTP/HTTPS
▼
┌───────────────────────────────────────┐
│ Web应用服务器 │
│ ┌───────────┐ ┌───────────┐ │
│ │ 表示层 │ │ 业务逻辑层│ │
│ │ (JSP) │ │ (Service) │ │
│ └───────────┘ └───────────┘ │
│ ▲ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ 数据访问层│ │
│ │ (MyBatis) │ │
│ └───────────┘ │
└───────────────────────────────────────┘
▲
│ JDBC
▼
┌───────────────────────────────────────┐
│ MySQL数据库 │
└───────────────────────────────────────┘
这种架构的优势在于:
- 分层明确,便于团队协作开发
- 前后端分离,便于后期维护和扩展
- 使用成熟框架,降低开发风险
3. 数据库设计与实现
3.1 核心表结构
系统数据库包含以下主要表:
学生表(student)
sql复制CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_no` varchar(20) NOT NULL COMMENT '学号',
`name` varchar(50) NOT NULL COMMENT '姓名',
`gender` char(1) DEFAULT NULL COMMENT '性别',
`class_id` int(11) DEFAULT NULL COMMENT '班级ID',
`password` varchar(100) NOT NULL COMMENT '密码(MD5加密)',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_student_no` (`student_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
教师表(teacher)
sql复制CREATE TABLE `teacher` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`teacher_no` varchar(20) NOT NULL COMMENT '工号',
`name` varchar(50) NOT NULL COMMENT '姓名',
`title` varchar(20) DEFAULT NULL COMMENT '职称',
`department_id` int(11) DEFAULT NULL COMMENT '院系ID',
`password` varchar(100) NOT NULL COMMENT '密码',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_teacher_no` (`teacher_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
课程表(course)
sql复制CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`course_code` varchar(20) NOT NULL COMMENT '课程代码',
`name` varchar(100) NOT NULL COMMENT '课程名称',
`credit` decimal(3,1) NOT NULL COMMENT '学分',
`hours` int(11) NOT NULL COMMENT '学时',
`teacher_id` int(11) NOT NULL COMMENT '授课教师ID',
`max_students` int(11) NOT NULL COMMENT '最大选课人数',
`current_students` int(11) DEFAULT '0' COMMENT '当前选课人数',
`schedule` text COMMENT '上课时间安排',
`classroom` varchar(50) DEFAULT NULL COMMENT '教室',
`description` text COMMENT '课程描述',
`cover_image` varchar(255) DEFAULT NULL COMMENT '封面图片',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_course_code` (`course_code`),
KEY `idx_teacher_id` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
选课记录表(selection)
sql复制CREATE TABLE `selection` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) NOT NULL COMMENT '学生ID',
`course_id` int(11) NOT NULL COMMENT '课程ID',
`select_time` datetime NOT NULL COMMENT '选课时间',
`score` decimal(5,2) DEFAULT NULL COMMENT '成绩',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1:正常 0:退课)',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_student_course` (`student_id`,`course_id`),
KEY `idx_course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 数据库优化策略
-
索引设计:
- 为所有外键字段添加索引
- 为高频查询条件字段添加索引
- 使用复合索引优化多条件查询
-
事务处理:
java复制@Transactional
public boolean selectCourse(int studentId, int courseId) {
// 检查课程是否已满
Course course = courseMapper.selectById(courseId);
if(course.getCurrentStudents() >= course.getMaxStudents()) {
throw new RuntimeException("课程已满");
}
// 检查是否已选该课程
if(selectionMapper.countByStudentAndCourse(studentId, courseId) > 0) {
throw new RuntimeException("不能重复选课");
}
// 创建选课记录
Selection selection = new Selection();
selection.setStudentId(studentId);
selection.setCourseId(courseId);
selection.setSelectTime(new Date());
selectionMapper.insert(selection);
// 更新课程当前人数
course.setCurrentStudents(course.getCurrentStudents() + 1);
courseMapper.updateById(course);
return true;
}
- 缓存策略:
- 使用Redis缓存热门课程信息
- 实现二级缓存减少数据库访问
4. 核心功能实现
4.1 选课流程实现
选课是系统的核心功能,需要考虑并发控制和数据一致性:
- 选课时序图:
code复制学生 -> 系统: 请求选课
系统 -> 数据库: 检查课程余量
数据库 --> 系统: 返回余量信息
alt 有余量
系统 -> 数据库: 创建选课记录
系统 -> 数据库: 减少课程余量
系统 --> 学生: 返回选课成功
else 无余量
系统 --> 学生: 返回课程已满
end
- 并发控制方案:
- 使用数据库乐观锁(version字段)
- 关键操作添加事务注解
- 高并发时段使用队列削峰
4.2 课程冲突检测
实现算法主要步骤:
- 获取学生已选课程的上课时间
- 解析目标课程的上课时间
- 检查时间是否有重叠
关键代码实现:
java复制public boolean checkScheduleConflict(int studentId, Course targetCourse) {
// 获取学生已选课程
List<Course> selectedCourses = courseMapper.selectByStudent(studentId);
// 解析目标课程时间
Schedule targetSchedule = parseSchedule(targetCourse.getSchedule());
for(Course course : selectedCourses) {
Schedule schedule = parseSchedule(course.getSchedule());
if(schedule.overlaps(targetSchedule)) {
return true; // 存在冲突
}
}
return false; // 无冲突
}
4.3 成绩管理模块
教师成绩录入功能需要考虑:
- 成绩校验规则
- 成绩分布统计
- 成绩修改日志
实现方案:
java复制@Transactional
public void inputScores(int courseId, Map<Integer, BigDecimal> scores) {
// 验证教师权限
Teacher teacher = getCurrentTeacher();
if(!courseMapper.checkTeacherCourse(teacher.getId(), courseId)) {
throw new RuntimeException("无权限操作该课程");
}
// 批量录入成绩
for(Map.Entry<Integer, BigDecimal> entry : scores.entrySet()) {
Selection selection = new Selection();
selection.setCourseId(courseId);
selection.setStudentId(entry.getKey());
selection.setScore(entry.getValue());
// 记录操作日志
ScoreLog log = new ScoreLog();
log.setCourseId(courseId);
log.setStudentId(entry.getKey());
log.setOldScore(selectionMapper.getScore(courseId, entry.getKey()));
log.setNewScore(entry.getValue());
log.setOperator(teacher.getId());
log.setOperateTime(new Date());
scoreLogMapper.insert(log);
// 更新成绩
selectionMapper.updateScore(selection);
}
}
5. 系统安全设计
5.1 认证与授权
-
登录认证:
- 使用Spring Security框架
- 密码采用BCrypt加密存储
- 实现验证码防止暴力破解
-
权限控制:
java复制@PreAuthorize("hasRole('TEACHER') and @securityService.isCourseTeacher(#courseId)")
@PostMapping("/courses/{courseId}/scores")
public ResponseEntity<?> inputScores(@PathVariable int courseId,
@RequestBody Map<Integer, Double> scores) {
// 教师只能给自己教授的课程录入成绩
scoreService.inputScores(courseId, scores);
return ResponseEntity.ok().build();
}
5.2 数据安全
-
SQL注入防护:
- 使用MyBatis参数化查询
- 禁止拼接SQL语句
-
XSS防护:
- 前端使用DOMPurify过滤
- 后端对用户输入进行转义
-
CSRF防护:
- 启用Spring Security的CSRF保护
- 敏感操作使用POST请求
6. 系统测试与优化
6.1 测试策略
-
单元测试:
- 使用JUnit + Mockito
- 核心方法测试覆盖率>80%
-
集成测试:
- 测试各模块协同工作
- 使用TestNG组织测试用例
-
性能测试:
- 使用JMeter模拟高并发选课
- 优化后要求:支持500并发/秒
6.2 性能优化记录
-
数据库优化:
- 添加合适的索引
- 优化慢查询
- 使用连接池
-
缓存优化:
- 课程信息缓存
- 选课结果缓存
-
前端优化:
- 静态资源CDN加速
- 启用Gzip压缩
- 合并CSS/JS文件
7. 部署方案
7.1 生产环境配置
服务器配置:
- 应用服务器:Tomcat 9.x
- 数据库服务器:MySQL 5.7主从架构
- 缓存服务器:Redis集群
- 操作系统:CentOS 7
部署架构:
code复制 ┌───────────────┐
│ Nginx │
│ (负载均衡) │
└───────────────┘
▲
│
┌──────────────┴──────────────┐
│ │
┌───────────────┐ ┌───────────────┐
│ Tomcat节点1 │ │ Tomcat节点2 │
└───────────────┘ └───────────────┘
▲ ▲
│ │
┌───────────────────────┐ ┌───────────────────────┐
│ MySQL主库 │ │ MySQL从库 │
└───────────────────────┘ └───────────────────────┘
▲
│
┌───────────────────────┐
│ Redis集群 │
└───────────────────────┘
7.2 持续集成部署
使用Jenkins实现自动化部署:
- 代码提交触发构建
- 运行单元测试
- 打包部署到测试环境
- 自动化测试
- 部署到生产环境
8. 项目总结与改进方向
在实际开发过程中,我们遇到了几个关键挑战:
-
选课高峰期性能问题:
- 初期设计没有充分考虑高并发场景
- 解决方案:引入Redis缓存、优化数据库事务
-
课程冲突检测算法:
- 复杂课程时间模式难以解析
- 最终采用正则表达式+时间区间算法
-
权限管理复杂性:
- 不同角色权限交叉复杂
- 使用Spring Security结合自定义注解解决
未来改进方向:
- 引入微服务架构,提高系统扩展性
- 增加移动端支持(小程序/APP)
- 实现智能推荐选课功能
- 接入学校统一身份认证系统
这个项目让我深刻体会到,一个看似简单的选课系统,背后需要考虑的技术细节和业务逻辑非常复杂。特别是在高并发场景下的数据一致性问题,需要设计合理的锁策略和事务机制。建议后续开发者可以在项目初期就做好压力测试,提前发现性能瓶颈。