☰
HOJ系统班级同步题单功能设计与实现
2026/10/26 14:55:12 网站建设 项目流程

1. HOJ系统与班级同步题单功能概述

HOJ(Hangzhou Online Judge)作为国内高校广泛采用的在线判题系统,其核心功能是为程序设计课程提供自动化评测服务。在计算机专业教学中,教师通常需要为不同班级布置差异化的编程练习,而原生HOJ系统缺乏班级维度的题单管理能力,导致教师不得不为每个班级重复创建相同题目或手动维护Excel表格记录分配情况。

班级同步题单功能的开发目标,是通过在HOJ系统中建立班级与题单的关联关系,实现:

  • 教师可一键将题单同步到指定班级
  • 系统自动记录各班级的题目完成进度
  • 支持按班级维度统计学生提交情况
  • 避免教师重复创建相同题单的工作量

实际教学中发现,同一门课程不同班级的教学进度常存在1-2周的差异,传统手动分配方式极易出现题目错配情况。本功能正是为解决这一痛点而生。

2. 功能架构设计与技术选型

2.1 数据库扩展方案

在原有HOJ数据库基础上新增三张核心表:

CREATE TABLE `class` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '班级名称', `teacher_id` int NOT NULL COMMENT '负责教师', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `problem_set_class` ( `id` int NOT NULL AUTO_INCREMENT, `problem_set_id` int NOT NULL COMMENT '题单ID', `class_id` int NOT NULL COMMENT '班级ID', `start_time` datetime NOT NULL COMMENT '开放时间', `end_time` datetime NOT NULL COMMENT '截止时间', PRIMARY KEY (`id`), UNIQUE KEY `uniq_ps_class` (`problem_set_id`,`class_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `class_student` ( `id` int NOT NULL AUTO_INCREMENT, `class_id` int NOT NULL, `user_id` int NOT NULL COMMENT '学生用户ID', PRIMARY KEY (`id`), UNIQUE KEY `uniq_class_user` (`class_id`,`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键设计考虑:

  1. 采用关联表而非字段扩展方式,保持与原有题单表的解耦
  2. 设置唯一索引防止重复关联
  3. 时间字段精确到分钟级,适应不同课程安排需求

2.2 前端交互优化

在题单管理页面增加班级选择组件:

<template> <el-select v-model="selectedClasses" multiple filterable placeholder="选择同步班级"> <el-option v-for="class in teacherClasses" :key="class.id" :label="class.name" :value="class.id"> </el-option> </el-select> <el-date-picker v-model="timeRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"> </el-date-picker> </template>

实现细节:

  • 采用Element UI的多选组件支持批量操作
  • 时间选择器精确到分钟级别
  • 异步加载教师所属班级列表

3. 核心业务逻辑实现

3.1 题单同步接口开发

@PostMapping("/problem-set/sync-to-class") public Result<?> syncToClass( @RequestParam Integer problemSetId, @RequestParam List<Integer> classIds, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") Date startTime, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") Date endTime) { // 验证题单归属 ProblemSet ps = problemSetService.getById(problemSetId); if (!ps.getCreator().equals(SecurityUtil.getUserId())) { return Result.error("无操作权限"); } // 批量创建关联关系 List<ProblemSetClass> list = new ArrayList<>(); for (Integer classId : classIds) { ProblemSetClass psc = new ProblemSetClass() .setProblemSetId(problemSetId) .setClassId(classId) .setStartTime(startTime) .setEndTime(endTime); list.add(psc); } problemSetClassService.saveBatch(list); return Result.ok(); }

关键安全控制:

  1. 校验当前用户是否为题单创建者
  2. 防止重复提交的幂等设计
  3. 事务保证批量插入的原子性

3.2 班级维度统计查询

SELECT u.username, u.real_name, COUNT(DISTINCT s.problem_id) AS solved_count, MAX(s.submit_time) AS last_submit_time FROM class_student cs JOIN user u ON cs.user_id = u.user_id LEFT JOIN submission s ON u.user_id = s.user_id AND s.problem_id IN ( SELECT problem_id FROM problem_set_problem WHERE problem_set_id = #{problemSetId} ) AND s.submit_time BETWEEN #{startTime} AND #{endTime} AND s.result = 0 -- AC状态 WHERE cs.class_id = #{classId} GROUP BY u.user_id ORDER BY solved_count DESC;

性能优化点:

  • 使用LEFT JOIN保留未提交学生记录
  • 子查询限定当前题单题目范围
  • 按班级ID分片查询

4. 实际应用中的经验总结

4.1 批量操作时的性能陷阱

初期实现直接循环插入数据库,当同步到20+班级时接口响应超过5秒。优化方案:

  1. 采用MyBatis的批量插入语法
  2. 添加@Transactional注解保证事务
  3. 增加异步任务队列处理超大规模同步
@Transactional public void batchInsert(List<ProblemSetClass> list) { String sql = "INSERT INTO problem_set_class (problem_set_id, class_id, start_time, end_time) VALUES "; StringBuilder values = new StringBuilder(); for (int i = 0; i < list.size(); i++) { values.append("(?,?,?,?)"); if (i < list.size() - 1) { values.append(","); } } jdbcTemplate.update(sql + values, list.stream().flatMap(psc -> Stream.of( psc.getProblemSetId(), psc.getClassId(), psc.getStartTime(), psc.getEndTime() )).toArray()); }

4.2 时间冲突校验的边界情况

实际使用中发现多个题单时间范围重叠会导致学生界面显示混乱。最终增加校验规则:

  1. 同一班级的同一题目不能存在时间重叠的题单
  2. 同步时自动检测并提示冲突
  3. 提供"强制覆盖"选项供特殊场景使用
public boolean checkTimeConflict(Integer classId, Date newStart, Date newEnd) { return problemSetClassMapper.exists(new QueryWrapper<ProblemSetClass>() .eq("class_id", classId) .apply("(start_time < {0} AND end_time > {1})", newEnd, newStart)); }

5. 功能扩展与未来优化方向

当前已实现班级与题单的基础关联,后续可扩展:

  1. 题单模板功能:将常用题单保存为模板,支持跨学期快速复用
  2. 智能推荐:根据班级历史表现自动推荐适当难度的题目
  3. 移动端适配:增加微信小程序端的题单管理能力

特别在模板功能实现上,建议采用如下数据结构:

CREATE TABLE `problem_set_template` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `creator_id` int NOT NULL, `tags` varchar(255) DEFAULT NULL COMMENT '标签分类', `is_public` tinyint DEFAULT 0 COMMENT '是否公开', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `problem_set_template_item` ( `id` int NOT NULL AUTO_INCREMENT, `template_id` int NOT NULL, `problem_id` int NOT NULL, `sort` int DEFAULT 0 COMMENT '题目排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这种设计既保持了与现有系统的兼容性,又能支持更灵活的教学资源复用。在实际编码时,建议采用策略模式来处理不同来源(模板/手动创建)的题单生成逻辑,避免if-else的硬编码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询