1. HOJ系统班级同步题单功能开发背景
HOJ(Hangzhou Online Judge)作为高校常用的在线判题系统,在教学实践中面临着课程管理与题目分发的实际需求。去年我在某高校计算机系担任助教时,每次实验课都需要手动为5个班级近200名学生逐个分配题目,这个过程不仅耗时费力,还容易出现遗漏。现有的HOJ开源版本虽然支持题单创建,但缺乏与教学班级的联动机制,这正是我们决定进行二次开发的核心动因。
班级同步题单功能的本质是建立"课程-班级-题单"的三级关联体系。通过分析教育场景中的实际工作流,我们发现教师通常需要:
- 按教学进度统一发布练习题
- 区分不同班级的题目难度
- 实时跟踪各班级完成情况
- 批量管理学生提交记录
2. 系统架构设计与技术选型
2.1 基础环境配置
开发基于HOJ v3.0.0版本,技术栈保持原有组合:
- 前端:Vue2 + ElementUI
- 后端:Spring Boot 2.5.6
- 数据库:MySQL 8.0
- 判题机:原版Docker方案
提示:建议在开发分支(如dev-class)进行修改,避免污染主代码库。我们团队在初期曾因直接修改master分支导致后续合并冲突,损失了3天工作量。
2.2 数据库扩展设计
在原有表结构基础上新增3张核心表:
sql复制CREATE TABLE `class` (
`cid` bigint NOT NULL AUTO_INCREMENT COMMENT '班级ID',
`class_name` varchar(50) DEFAULT NULL COMMENT '班级名称',
`teacher_uid` varchar(32) DEFAULT NULL COMMENT '教师ID',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `class_problem_list` (
`id` bigint NOT NULL AUTO_INCREMENT,
`cid` bigint DEFAULT NULL COMMENT '班级ID',
`pid` bigint DEFAULT NULL COMMENT '题单ID',
`gmt_create` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cid_pid_unique` (`cid`,`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `class_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`cid` bigint DEFAULT NULL COMMENT '班级ID',
`uid` varchar(32) DEFAULT NULL COMMENT '用户ID',
`gmt_create` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cid_uid_unique` (`cid`,`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.3 关键接口设计
后端新增的核心接口包括:
-
班级管理接口
/api/class/create(POST) 创建班级/api/class/delete/{cid}(DELETE) 解散班级/api/class/add-user(POST) 添加学生
-
题单同步接口
/api/class/problem-list/sync(POST) 同步题单到班级/api/class/problem-list/status(GET) 获取班级题单完成情况
-
前端路由新增:
/class-management班级管理页/class-problem-list班级题单页
3. 核心功能实现细节
3.1 班级成员批量导入
开发中遇到的最大挑战是学生名单导入功能。我们支持Excel模板导入,但实际使用中发现三个典型问题:
- 学号与HOJ账号的映射关系混乱
- 表格格式不统一导致解析失败
- 批量插入时的性能瓶颈
最终解决方案:
java复制// 使用EasyExcel处理导入文件
public void importClassUsers(MultipartFile file, Long cid) {
// 1. 校验文件格式
if (!file.getOriginalFilename().endsWith(".xlsx")) {
throw new RuntimeException("仅支持.xlsx格式文件");
}
// 2. 使用监听器模式处理大数据量
EasyExcel.read(file.getInputStream(), ClassUserImportDTO.class,
new ClassUserImportListener(cid)).sheet().doRead();
// 3. 批量插入采用事务+批处理
try {
List<ClassUser> batchList = new ArrayList<>(BATCH_SIZE);
for (ClassUser user : importUsers) {
batchList.add(user);
if (batchList.size() >= BATCH_SIZE) {
classUserService.saveBatch(batchList);
batchList.clear();
}
}
if (!batchList.isEmpty()) {
classUserService.saveBatch(batchList);
}
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException("导入失败:" + e.getMessage());
}
}
3.2 题单同步的并发控制
当多个教师同时操作班级题单时,可能出现并发问题。我们采用乐观锁机制解决:
java复制@Transactional
public boolean syncProblemList(Long cid, Long pid) {
// 检查班级是否存在
Class classInfo = classMapper.selectById(cid);
if (classInfo == null) {
throw new RuntimeException("班级不存在");
}
// 检查题单是否存在
ProblemList problemList = problemListMapper.selectById(pid);
if (problemList == null) {
throw new RuntimeException("题单不存在");
}
// 乐观锁控制
ClassProblemList relation = new ClassProblemList();
relation.setCid(cid);
relation.setPid(pid);
relation.setGmtCreate(new Date());
try {
return classProblemListMapper.insert(relation) > 0;
} catch (DuplicateKeyException e) {
log.warn("题单已存在于班级中:cid={}, pid={}", cid, pid);
return false;
}
}
4. 前端交互优化实践
4.1 班级题单可视化看板
使用ECharts实现班级做题情况统计视图:
javascript复制// 在vue组件中初始化图表
initChart() {
const chartDom = this.$refs.chartDom;
const myChart = echarts.init(chartDom);
this.$api.getClassProblemStatus(this.cid).then(res => {
const option = {
tooltip: { trigger: 'item' },
series: [{
name: '完成情况',
type: 'pie',
radius: ['40%', '70%'],
data: [
{ value: res.data.finished, name: '已完成' },
{ value: res.data.total - res.data.finished, name: '未完成' }
],
label: { formatter: '{b}: {c} ({d}%)' }
}]
};
myChart.setOption(option);
});
}
4.2 批量操作体验优化
通过以下设计提升教师使用效率:
- 题单多选后批量同步到班级
- 学生列表支持拖拽排序
- 操作结果实时Toast提示
- 重要操作二次确认弹窗
vue复制<template>
<el-transfer
v-model="selectedUsers"
:data="userList"
:titles="['未分配学生', '班级成员']"
@change="handleUserChange">
</el-transfer>
</template>
<script>
export default {
methods: {
async handleUserChange(_, direction, movedKeys) {
if (direction === 'right') {
await this.$api.addClassUsers(this.cid, movedKeys);
} else {
await this.$api.removeClassUsers(this.cid, movedKeys);
}
this.$message.success(direction === 'right' ? '添加成功' : '移除成功');
}
}
}
</script>
5. 部署与测试要点
5.1 灰度发布方案
为避免影响线上服务,我们采用分阶段发布:
- 先在测试环境验证基础功能
- 选择2个班级进行beta测试
- 全量发布前进行压力测试
测试重点包括:
- 500人班级的题单同步性能
- 并发导入1000个学生的稳定性
- 不同角色(教师/助教/学生)的权限控制
5.2 性能优化记录
通过Jmeter测试发现的性能瓶颈及解决方案:
| 场景 | 初始QPS | 优化措施 | 优化后QPS |
|---|---|---|---|
| 题单同步 | 12 | 添加Redis缓存 | 85 |
| 名单导入 | 8 | 改用批量插入 | 32 |
| 统计查询 | 15 | 增加数据库索引 | 110 |
关键索引添加SQL:
sql复制ALTER TABLE `class_problem_list` ADD INDEX `idx_cid` (`cid`);
ALTER TABLE `class_user` ADD INDEX `idx_cid_uid` (`cid`, `uid`);
6. 实际教学应用案例
在某数据结构课程中,教师按周同步题单的效果对比:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 题目分发时间 | 45分钟/班 | 2分钟/所有班 |
| 学生完成率 | 68% | 92% |
| 错题集中度 | 分散 | 可班级维度统计 |
| 作业批改效率 | 3小时/次 | 1小时/次 |
典型使用流程:
- 教师创建"二叉树基础"题单
- 同步到3个平行班级
- 设置截止时间为周五23:59
- 系统自动统计各班级完成情况
- 根据错题率调整下周教学内容
7. 扩展开发建议
基于现有架构可进一步扩展:
- 与课程管理系统对接
- 增加自动组卷功能
- 开发错题本功能
- 支持题目难度动态调整
java复制// 难度调整示例代码
public void autoAdjustProblemDifficulty(Long pid) {
// 获取班级平均通过率
Double avgPassRate = judgeEntityService.getAvgPassRateByPid(pid);
// 根据通过率动态调整难度
if (avgPassRate > 0.8) {
problemManager.increaseDifficulty(pid);
} else if (avgPassRate < 0.3) {
problemManager.decreaseDifficulty(pid);
}
}
在三个月的实际使用中,这套班级同步系统帮助教师节省了约60%的题目管理时间。有个细节值得注意:最初我们设计的批量导入没有进度提示,导致教师频繁刷新页面。后来增加WebSocket实时推送进度后,用户投诉率下降了90%。这提醒我们,在教育类系统的开发中,操作反馈的即时性比想象中更重要
