跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java算法

在线 OJ 系统竞赛管理模块实战 (Java/Spring/Vue)

基于 Java Spring Boot 与 Vue 构建的在线 OJ 系统竞赛管理模块,涵盖数据库设计、前后端交互及核心业务逻辑。实现内容包括竞赛列表查询、新增与编辑、题目关联管理、状态发布控制等。技术细节涉及 MyBatis Plus 分页、Jackson 序列化处理、批量数据插入优化及并发状态校验,确保竞赛流程的完整性与数据一致性。

奶糖兔发布于 2026/3/23更新于 2026/8/1745 浏览
在线 OJ 系统竞赛管理模块实战 (Java/Spring/Vue)

数据库设计

竞赛管理涉及竞赛基础信息与题目关联,核心表结构如下:

create table tb_exam (
    exam_id bigint unsigned not null comment '竞赛 id(主键)',
    title varchar(50) not null comment '竞赛标题',
    start_time datetime not null comment '竞赛开始时间',
    end_time datetime not null comment '竞赛结束时间',
    status tinyint not null default '0' comment '是否发布 0:未发布 1:已发布',
    create_by bigint unsigned not null comment '创建人',
    create_time datetime not null comment '创建时间',
    update_by bigint unsigned comment '更新人',
    update_time datetime comment '更新时间',
    primary key(exam_id)
);

create table tb_exam_question (
    exam_question_id bigint unsigned not null comment '竞赛题目关系 id(主键)',
    question_id bigint unsigned not null comment '题目 id(主键)',
    exam_id bigint unsigned not null comment '竞赛 id(主键)',
    question_order int not null comment '题目顺序',
    create_by bigint unsigned not null comment '创建人',
    create_time datetime not null comment '创建时间',
    update_by bigint unsigned comment '更新人',
    update_time datetime comment '更新时间',
    primary key(exam_question_id)
);

竞赛列表查询

后端实现

Controller 层直接复用通用的 BaseController,Service 层调用分页插件进行数据检索。前端传入的 DTO 需要继承 pageDomain 以支持分页参数。

@RestController
@RequestMapping("/exam")
public class ExamController extends BaseController {
    @Autowired
    private IExamService examService;

    @GetMapping("/list")
    public TableDataInfo list(ExamQueryDTO examQueryDTO) {
        return getDataTable(examService.list(examQueryDTO));
    }
}

Mapper 层由于查询条件较为复杂(包含多表关联),采用 XML 方式编写 SQL。注意日期时间的比较逻辑以及用户昵称的关联查询。

<select resultType="com.bite.system.model.exam.vo.ExamVO">
    SELECT te.exam_id, te.title, te.start_time, te.end_time, te.create_time, ts.nick_name as create_name, te.status 
    FROM tb_exam te 
    left join tb_sys_user ts on te.create_by = ts.user_id
    <where>
        <if test="title !=null and title !='' ">
            AND te.title LIKE CONCAT('%',#{title},'%')
        </if>
        <if test="startTime != null and startTime != '' ">
            AND te.start_time >= #{startTime}
        </if>
        <if test="endTime != null and endTime != ''">
            AND te.end_time &lt;= #{endTime}
        </if>
    </where>
    ORDER BY te.create_time DESC
</select>

VO 序列化细节

返回给前端的 VO 对象中,examId 使用了 @JsonSerialize(using = ToStringSerializer.class)。这是因为雪花算法生成的 ID 可能超过 JavaScript Number 类型的精度范围,转为字符串传输可避免精度丢失。同时,日期字段使用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 统一格式。

文章配图

新增与编辑竞赛

业务逻辑

新增竞赛分为两步:先保存竞赛基本信息获取 ID,再关联题目。这样设计是为了防止出现无标题的题目集合,同时也便于在添加过程中随时回滚或退出。

校验机制

在 Service 层需重点处理以下校验:

  1. 标题唯一性:排除当前编辑记录后检查是否存在重复标题。
  2. 时间逻辑:开始时间不能早于当前时间,且必须早于结束时间。
  3. 状态保护:一旦竞赛发布或已开始,禁止修改题目或基本信息。
@Override
public String add(ExamAddDTO examAddDTO) {
    checkExamSaveParams(examAddDTO, null);
    Exam exam = new Exam();
    BeanUtil.copyProperties(examAddDTO, exam);
    examMapper.insert(exam);
    return exam.getExamId().toString();
}

private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
    List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
        .eq(Exam::getTitle, examSaveDTO.getTitle())
        .ne(examId != null, Exam::getExamId, examId));
    if (CollectionUtil.isNotEmpty(examList)) {
        throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
    }
    if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
        throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
    }
    if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
        throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
    }
}

前端交互

前端页面采用分步式布局,先填写基础信息并保存,随后通过弹窗选择题目。时间选择器返回的是数组,提交时需拆分为 startTime 和 endTime 两个参数。

async function saveBaseInfo() {
    const fd = new FormData()
    for (let key in formExam) {
        if (key === 'examDate') {
            fd.append('startTime', formExam.examDate[0])
            fd.append('endTime', formExam.examDate[1])
        } else {
            fd.append(key, formExam[key])
        }
    }
    await examAddService(fd)
    ElMessage.success('基本信息保存成功')
}

题目关联管理

批量添加

后端接收题目 ID 集合,首先校验竞赛是否存在及状态是否允许修改。接着批量查询题目信息,确保所有 ID 有效,最后利用自定义的 saveBatch 方法将竞赛与题目的关系写入中间表。

@Override
public boolean questionAdd(ExamQuestAddDTO examQuestAddDTO) {
    Exam exam = getExam(examQuestAddDTO.getExamId());
    checkExam(exam); // 检查状态
    Set<Long> questionIdSet = examQuestAddDTO.getQuestionIdSet();
    if (CollectionUtil.isEmpty(questionIdSet)) return true;
    
    List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
    if (CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {
        throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
    }
    return saveExamQuestion(exam, questionIdSet);
}

题目删除与过滤

删除题目时同样需要校验竞赛状态。在前端搜索题目时,为了避免重复添加,后端接口增加了 excludeIdStr 参数,用于排除已选中的题目 ID。

<if test="excludeIdSet !=null and !excludeIdSet.isEmpty()">
    <foreach collection="excludeIdSet" open=" AND tq.question_id NOT IN( " close=" ) " item="id" separator=",">
        #{id}
    </foreach>
</if>

状态流转控制

发布与撤销

发布竞赛前需校验结束时间是否已过,且必须包含至少一道题目。撤销发布则只需重置状态位。

@Override
public int publish(Long examId) {
    Exam exam = getExam(examId);
    if (exam.getEndTime().isBefore(LocalDateTime.now())) {
        throw new ServiceException(ResultCode.EXAM_IS_FINISH);
    }
    Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
    if (count == null || count <= 0) {
        throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
    }
    exam.setStatus(Contants.TRUE);
    return examMapper.updateById(exam);
}

删除操作

删除竞赛前同样校验状态,若已发布则禁止删除。删除时会级联清理 tb_exam_question 中的关联数据,保证数据一致性。

文章配图

目录

  1. 数据库设计
  2. 竞赛列表查询
  3. 后端实现
  4. VO 序列化细节
  5. 新增与编辑竞赛
  6. 业务逻辑
  7. 校验机制
  8. 前端交互
  9. 题目关联管理
  10. 批量添加
  11. 题目删除与过滤
  12. 状态流转控制
  13. 发布与撤销
  14. 删除操作
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Vue3 项目实战:Axios 基础封装与接口调用
  • GitHub 学生认证与 PyCharm 配置 Copilot 全流程指南
  • Rust 内联汇编:原理与实战
  • Qt与Web混合编程:CEF与QCefView深度解析
  • Spring Boot 结合 jQuery 实现前后端分离图书管理系统
  • 使用大语言模型从零构建知识图谱
  • 零基础学习 Python 必备开发工具与库指南
  • Llama-2-7b 在昇腾 NPU 上的六大核心场景性能基准报告
  • 基于 C++11 实现前端 Promise 模式
  • Soft Actor-Critic (SAC) 算法原理与 PyTorch 实现
  • CentOS 7.9 Docker 安装、配置与实战指南
  • RabbitMQ/Spring-AMQP 高级特性:事务机制与消息限流
  • MCP 插件使用指南:以 browser-tools-mcp 为例
  • AI 幻觉详解:大模型为何会一本正经地胡说八道?
  • C++ 实现 WAV 与 MP3 音频播放功能
  • 利用检索增强生成(RAG)降低大模型幻觉与虚假信息
  • Visual C++ Redistributable 运行环境配置与修复指南
  • CoPaw 与 OpenFang 免费开源 AI 工具部署指南
  • Clang Power Tools C++ 静态分析工具使用指南
  • 5 款国产免费 AI 代码助手评测对比

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online