课表管理系统是高校教务管理中的核心环节,传统手工排课方式存在诸多痛点。我在实际参与某高校信息化建设时,亲眼目睹教务老师每周需要花费20多个小时手工排课,经常出现教室冲突、教师时间冲突等问题。这种低效的管理模式催生了我们对自动化课表管理系统的开发需求。
现代教育机构对课表管理系统主要有以下几类核心需求:
多角色协同:系统需要支持管理员、教师、学生三类用户的不同操作权限。管理员负责基础数据维护和排课,教师需要查看个人课表和学生名单,学生则主要查询选课信息和课表。
智能排课:系统应能自动处理教室资源、教师时间、学生选课等多维度的约束条件,避免传统排课中的各类冲突。根据我们的调研,90%的教务投诉都源于排课冲突。
实时查询:移动互联网时代,师生需要随时随地通过PC或手机查看最新课表信息。系统响应速度直接影响用户体验。
经过对当前主流技术的对比测试,我们最终确定的技术方案如下:
后端技术栈:
前端技术栈:
技术选型心得:在初期我们考虑过使用Spring Cloud微服务架构,但实际测试发现对于中小型教育机构,单体应用配合良好的模块划分已经足够,且部署维护成本更低。
系统采用经典的前后端分离架构:
code复制┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Vue 3 │ │ Spring Boot │ │ MySQL │
│ 前端工程 │ ←→ │ 后端应用 │ ←→ │ 数据库 │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑
│ │
┌──────┴───────┐ ┌──────┴───────┐
│ Element │ │ Redis │
│ UI组件库 │ │ 缓存 │
└──────────────┘ └──────────────┘
前后端通过定义清晰的API文档进行协作,我们使用Swagger UI自动生成接口文档,极大提升了开发效率。
数据库设计是系统稳定性的基石,我们采用三范式设计原则,主要表结构如下:
sql复制CREATE TABLE `users` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password_hash` varchar(64) NOT NULL COMMENT '加密密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`role_type` tinyint NOT NULL COMMENT '1-管理员 2-教师 3-学生',
`college_id` int DEFAULT NULL COMMENT '所属院系',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `courses` (
`course_id` bigint NOT NULL AUTO_INCREMENT,
`course_code` varchar(20) NOT NULL COMMENT '课程编号',
`course_name` varchar(100) NOT NULL,
`credit` tinyint NOT NULL COMMENT '学分',
`teacher_id` bigint NOT NULL COMMENT '授课教师',
`max_students` int DEFAULT '100' COMMENT '最大选课人数',
`description` text COMMENT '课程描述',
PRIMARY KEY (`course_id`),
KEY `idx_teacher` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `schedules` (
`schedule_id` bigint NOT NULL AUTO_INCREMENT,
`course_id` bigint NOT NULL,
`classroom_id` bigint NOT NULL,
`week_day` tinyint NOT NULL COMMENT '1-7对应周一到周日',
`start_time` time NOT NULL,
`end_time` time NOT NULL,
`semester` varchar(20) NOT NULL COMMENT '如2023-2024-1',
`week_range` varchar(50) NOT NULL COMMENT '上课周次,如1-16',
PRIMARY KEY (`schedule_id`),
UNIQUE KEY `uk_classroom_time` (`classroom_id`,`week_day`,`start_time`,`semester`),
KEY `idx_course` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据库优化技巧:在课表表上我们创建了唯一索引uk_classroom_time,确保同一教室在同一时间段不会重复排课。同时为常用查询字段添加普通索引,提升查询性能。
排课算法的核心是解决多维约束问题,我们的实现方案如下:
java复制public class ScheduleGenerator {
// 排课核心方法
public List<Schedule> generateSchedules(List<Course> courses,
List<Classroom> classrooms,
List<Teacher> teachers) {
// 1. 初始化可用时间槽(每周5天,每天8个时段)
Map<Integer, List<TimeSlot>> availableSlots = initTimeSlots();
// 2. 按课程优先级排序(必修课优先)
courses.sort(Comparator.comparingInt(c -> c.isRequired() ? 0 : 1));
List<Schedule> result = new ArrayList<>();
for (Course course : courses) {
Teacher teacher = findTeacher(teachers, course.getTeacherId());
// 3. 查找满足条件的教室和时间组合
for (Classroom room : classrooms) {
if (!room.getType().equals(course.getRoomType())) continue;
for (int day = 1; day <= 5; day++) {
for (TimeSlot slot : availableSlots.get(day)) {
// 4. 检查教师时间冲突
if (isTeacherAvailable(teacher, day, slot)) {
// 5. 检查教室占用
if (isClassroomAvailable(room, day, slot)) {
Schedule schedule = createSchedule(course, room, day, slot);
result.add(schedule);
updateAvailability(availableSlots, teacher, room, day, slot);
break;
}
}
}
}
}
}
return result;
}
// 其他辅助方法...
}
算法特点:
系统采用RBAC(基于角色的访问控制)模型,结合Spring Security实现:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/teacher/**").hasAnyRole("TEACHER", "ADMIN")
.antMatchers("/api/student/**").hasAnyRole("STUDENT", "TEACHER", "ADMIN")
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
// 密码编码器配置
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
前端路由也做了相应控制,使用Vue Router的导航守卫:
javascript复制router.beforeEach((to, from, next) => {
const userRole = store.getters.userRole;
const requiredRole = to.meta.role;
if (requiredRole && !hasPermission(userRole, requiredRole)) {
next('/forbidden');
} else {
next();
}
});
function hasPermission(userRole, requiredRole) {
// 管理员拥有所有权限
if (userRole === 'ADMIN') return true;
// 教师可以访问学生页面
if (userRole === 'TEACHER' && requiredRole === 'STUDENT') return true;
return userRole === requiredRole;
}
在实现课表冲突检测时,我们遇到了几个典型问题:
同一教师同一时间被安排多门课程
sql复制SELECT COUNT(*) FROM schedules
WHERE teacher_id = ? AND week_day = ?
AND ((start_time BETWEEN ? AND ?) OR (end_time BETWEEN ? AND ?))
教室资源冲突
java复制public boolean isClassroomAvailable(Long classroomId, int weekDay, TimeSlot slot) {
return scheduleMapper.countByClassroomAndTime(
classroomId, weekDay, slot.getStart(), slot.getEnd()) == 0;
}
学生选课时间冲突
java复制public void checkStudentScheduleConflict(Long studentId, Long courseId) {
List<Schedule> mySchedules = getStudentSchedules(studentId);
Schedule newSchedule = getCourseSchedule(courseId);
for (Schedule s : mySchedules) {
if (isTimeOverlap(s, newSchedule)) {
throw new BusinessException("选课时间冲突");
}
}
}
随着数据量增长,我们遇到了几个性能瓶颈:
课表查询慢问题
schedule:student:{studentId}:{semester}排课算法效率问题
数据库连接池配置
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
使用Element UI的时间线组件结合自定义样式实现课表展示:
vue复制<template>
<div class="timetable">
<div v-for="day in weekDays" :key="day.value" class="day-column">
<div class="day-header">{{ day.label }}</div>
<div v-for="slot in timeSlots" :key="slot" class="time-slot">
<div v-if="getCourse(day.value, slot)"
class="course-card"
:style="getCardStyle(day.value, slot)">
{{ getCourse(day.value, slot).courseName }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
methods: {
getCardStyle(day, slot) {
const course = this.getCourse(day, slot);
if (!course) return {};
// 计算课程持续的时间段数
const duration = (course.endHour - course.startHour) * 2;
return {
height: `${duration * 50}px`,
backgroundColor: this.getCourseColor(course.courseId)
};
}
}
}
</script>
在课程添加表单中实现多层验证:
vue复制<el-form :model="courseForm" :rules="rules" ref="formRef">
<el-form-item label="课程代码" prop="courseCode">
<el-input v-model="courseForm.courseCode"></el-input>
</el-form-item>
<el-form-item label="课程名称" prop="courseName">
<el-input v-model="courseForm.courseName"></el-input>
</el-form-item>
<el-form-item label="学分" prop="credit">
<el-input-number v-model="courseForm.credit" :min="1" :max="10"></el-input-number>
</el-form-item>
</el-form>
<script>
export default {
data() {
return {
rules: {
courseCode: [
{ required: true, message: '请输入课程代码' },
{ pattern: /^[A-Z]{2}\d{4}$/, message: '格式如CS1010' }
],
courseName: [
{ required: true, message: '请输入课程名称' },
{ max: 50, message: '不超过50字符' }
]
}
}
}
}
</script>
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql:3306/schedule_db
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=schedule_db
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
mysql_data:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
javascript复制import * as Sentry from '@sentry/vue';
Sentry.init({
dsn: 'https://example@sentry.io/1',
integrations: [new BrowserTracing()],
tracesSampleRate: 0.2
});
经过三个月的开发和两个月的试运行,系统在某高校成功上线,支持了5000+师生用户的日常使用。主要技术指标如下:
后续改进方向:
在开发过程中,最大的收获是认识到良好的系统设计需要平衡技术先进性和实际业务需求。例如最初我们计划使用微服务架构,但考虑到学校IT部门的运维能力,最终选择了更易维护的单体架构。这种务实的技术决策对项目成功至关重要。