OJ在线评测系统 后端 用策略模式优化判题机架构
判题机架构优化(策略模式)
思考
我们的判题策略可能会有很多种 比如 我们的代码沙箱本身执行程序需要消耗时间
这个时间可能不同的编程语言是不同的 比如沙箱执行Java要额外花费2秒
我们可以采用策略模式 针对不同的情况 定义不同独立的策略
而不是把所有情况全部放在一个if else里面
定义一个策略接口
我們先写一个方法
这个方法传入的是一个上下文对象context

package com.dduo.dduoj.judge.strategy;import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;/*
* 判题策略
* */
public interface JudgeStrategy {/** 执行判题* @param judgeContext* @return* */JudgeInfo doJudge(JudgeContext judgeContext);}
创建这个上下文对象
我们传入的数据 有判题状态 输入用例 输出用例
用于定义在策略中传递的参数

package com.dduo.dduoj.judge.strategy;import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;
import com.dduo.dduoj.model.entity.Question;
import lombok.Data;import java.util.List;/*
* 上下文 用于定义在策略中传递的参数
* */@Data
public class JudgeContext {private JudgeInfo judgeInfo;private List<String> inputList;private List<String> outputList;private Question question;}
接下来我们要把刚刚写的判题逻辑部分的代码搬到接口的实现类里面去

这边创建一个策略模式接口的实现类
默认实现类
类名为DefaultJudgeStrategy

我们先从上下文对象中获得信息

再进行判断

返回值
![]()
package com.dduo.dduoj.judge.strategy;import cn.hutool.json.JSONUtil;
import com.dduo.dduoj.model.dto.question.JudgeCase;
import com.dduo.dduoj.model.dto.question.JudgeConfig;
import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;
import com.dduo.dduoj.model.entity.Question;
import com.dduo.dduoj.model.enums.JudgeInfoMessageEnum;import javax.swing.*;
import java.util.List;public class DefaultJudgeStrategyImpl implements JudgeStrategy {/*** 执行判题* @param judgeContext* @return*/@Overridepublic JudgeInfo doJudge(JudgeContext judgeContext) {// 从上下文对象获取信息JudgeInfo judgeInfo = judgeContext.getJudgeInfo();List<String> inputList = judgeContext.getInputList();List<String> outputList = judgeContext.getOutputList();Question question = judgeContext.getQuestion();List<JudgeCase> judgeCaseList = judgeContext.getJudgeCaselist();// 从判题信息中获取信息Long memory = judgeInfo.getMemoryLimit();Long time = judgeInfo.getTime();JudgeInfo judgeInfoResponse = new JudgeInfo();JudgeInfoMessageEnum judgeInfoMessageEnum = JudgeInfoMessageEnum.Accepted;judgeInfoResponse.setMemoryLimit(memory);judgeInfoResponse.setTime(time);// 先判断沙箱执行的结果输出数量是否和预期输出数量相等if (outputList.size() != inputList.size()) {judgeInfoMessageEnum = JudgeInfoMessageEnum.Wrong_Answer;judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());return judgeInfoResponse;}// 依次判断每一项输出和预期输出是否相等for (int i = 0; i < judgeCaseList.size(); i++) {JudgeCase judgeCase = judgeCaseList.get(i);if (!judgeCase.getOutput().equals(outputList.get(i))) {judgeInfoMessageEnum = JudgeInfoMessageEnum.Wrong_Answer;judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());return judgeInfoResponse;}}// 判断题目限制String judgeConfigStr = question.getJudgeConfig();JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);Long needMemoryLimit = judgeConfig.getMemoryLimit();Long needTimeLimit = judgeConfig.getTimeLimit();if (memory > needMemoryLimit) {judgeInfoMessageEnum = JudgeInfoMessageEnum.Memory_Limit_Exceeded;judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());return judgeInfoResponse;}if (time > needTimeLimit) {judgeInfoMessageEnum = JudgeInfoMessageEnum.Memory_Limit_Exceeded;judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());return judgeInfoResponse;}judgeInfoResponse.setMessage(judgeInfoMessageEnum.getValue());return judgeInfoResponse;}
}
我们把所有判题的内容 放到一个单独的类里面 这个类叫做默认策略
接下来我们就可以在实现类里面用默认策略去解决
先根据沙箱的执行结果设置题目的判题状态和信息
再把上下文对象放到策略模式里面去

package com.dduo.dduoj.judge;import cn.hutool.json.JSONUtil;
import com.dduo.dduoj.common.ErrorCode;
import com.dduo.dduoj.exception.BusinessException;
import com.dduo.dduoj.judge.codesandbox.CodeSandbox;
import com.dduo.dduoj.judge.codesandbox.CodeSandboxFactory;
import com.dduo.dduoj.judge.codesandbox.CodeSandboxProxy;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeRequest;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeResponse;
import com.dduo.dduoj.judge.strategy.DefaultJudgeStrategy;
import com.dduo.dduoj.judge.strategy.JudgeContext;
import com.dduo.dduoj.judge.strategy.JudgeStrategy;
import com.dduo.dduoj.model.dto.question.JudgeCase;
import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;
import com.dduo.dduoj.model.entity.Question;
import com.dduo.dduoj.model.entity.QuestionSubmit;
import com.dduo.dduoj.model.enums.QuestionSubmitStatusEnum;
import com.dduo.dduoj.service.QuestionService;
import com.dduo.dduoj.service.QuestionSubmitService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;@Service
public class JudgeServiceImpl implements JudgeService {// 题目服务@Resourceprivate QuestionService questionService;// 题目提交服务@Resourceprivate QuestionSubmitService questionSubmitService;@Value("${codesandbox.type:example}")private String value;@Overridepublic QuestionSubmit doJudge(Long questionSubmitId) {QuestionSubmit questionSubmit = questionSubmitService.getById(questionSubmitId);if (questionSubmit == null) {throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "提交信息不存在");}//拿到题目提交信息Long questionId = questionSubmit.getQuestionId();//拿到题目Question question = questionService.getById(questionId);if (question == null) {throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "题目不存在");}// 题目存在// 开始判题// 更改题目的状态 status// 如果不为等待状态if (questionSubmit.getStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目正在判题中");}// 重新设置QuestionSubmit questionSubmitUpdate = new QuestionSubmit();questionSubmitUpdate.setId(questionSubmitId);questionSubmitUpdate.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());boolean judge = questionSubmitService.updateById(questionSubmitUpdate);if (!judge) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误");}// 接下来放代码沙箱CodeSandbox codeSandbox = CodeSandboxFactory.NewInstance(value);codeSandbox = new CodeSandboxProxy(codeSandbox);// 拿出数据String code = questionSubmit.getCode();String language = questionSubmit.getLanguage();// 获取输入用例String judgeCaseStr = question.getJudgeCase();List<JudgeCase> judgeCaselist = JSONUtil.toList(judgeCaseStr, JudgeCase.class);List<String> inputList = judgeCaselist.stream().map(JudgeCase::getInput).collect(Collectors.toList());ExecuteCodeRequest executeRequest = ExecuteCodeRequest.builder().code(code).language(language).inputList(inputList).build();ExecuteCodeResponse executeCodeResponse=codeSandbox.executeCode(executeRequest);List<String> outputList = executeCodeResponse.getOutputList();// 根据沙箱的执行结果 设置题目的判题状态和信息JudgeContext judgeContext = new JudgeContext();judgeContext.setJudgeInfo(executeCodeResponse.getJudgeInfo());judgeContext.setInputList(inputList);judgeContext.setOutputList(outputList);judgeContext.setQuestion(question);judgeContext.setJudgeCaselist(judgeCaselist);// 用默认策略去解决JudgeStrategy judgeStrategy=new DefaultJudgeStrategy();JudgeInfo judgeInfo = judgeStrategy.doJudge(judgeContext);// 修改数据库中的判题结果questionSubmitUpdate = new QuestionSubmit();questionSubmitUpdate.setId(questionSubmitId);questionSubmitUpdate.setStatus(QuestionSubmitStatusEnum.SUCCESS.getValue());questionSubmitUpdate.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));// 看数据库boolean update = questionSubmitService.updateById(questionSubmitUpdate);if (!update) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误");}// 返回结果QuestionSubmit questionSubmitResult = questionSubmitService.getById(questionId);return questionSubmitResult;}
}
刚才只是定义了默认策略
我们还要定义其他策略
如Java程序的执行的策略

接下来我们就要去想一想如何去切换策略

但是如果用简单的判断 如果有复杂的情况会很麻烦 而且 写的if else语句会变的很多
建议单独编写一个判断策略的方法 或者是类
工厂 map缓存
定义JudgeManager 目的是尽量简化对判题功能的调用
让调用方最简便
去操作

补全代码
实际上就是对我们选择合适的策略这个过程
或者是进行其他的判断的时候
进行了一个判断

package com.dduo.dduoj.judge;import com.dduo.dduoj.judge.strategy.DefaultJudgeStrategy;
import com.dduo.dduoj.judge.strategy.JavaLanguageJudgeStrategy;
import com.dduo.dduoj.judge.strategy.JudgeContext;
import com.dduo.dduoj.judge.strategy.JudgeStrategy;
import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;
import com.dduo.dduoj.model.entity.QuestionSubmit;/*
* 判题管理(简化调用)
* */
public class JudgeManger {/** 执行判题* @param judgeContext* @return* */JudgeInfo doJudge(JudgeContext judgeContext) {QuestionSubmit questionSubmit = judgeContext.getQuestionSubmit();String language = questionSubmit.getLanguage();JudgeStrategy judgeStrategy = new DefaultJudgeStrategy();if ("java".equals(language)) {judgeStrategy = new JavaLanguageJudgeStrategy();}return judgeStrategy.doJudge(judgeContext);}}
这样就行

在之前的题目提交实现类 核心逻辑里面

那么我们的核心逻辑就是这样的
@Override
public long doQuestionSubmit(QuestionSubmitAddRequest questionSubmitAddRequest, User loginUser) {// 校验编程语言是否合法String language = questionSubmitAddRequest.getLanguage();QuestionSubmitLanguageEnum languageEnum = QuestionSubmitLanguageEnum.getEnumByValue(language);if (languageEnum == null) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "编程语言错误");}long questionId = questionSubmitAddRequest.getQuestionId();// 判断实体是否存在,根据类别获取实体Question question = questionService.getById(questionId);if (question == null) {throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);}// 是否已提交题目long userId = loginUser.getId();// 每个用户串行提交题目QuestionSubmit questionSubmit = new QuestionSubmit();questionSubmit.setUserId(userId);questionSubmit.setQuestionId(questionId);questionSubmit.setCode(questionSubmitAddRequest.getCode());questionSubmit.setLanguage(language);// 设置初始状态questionSubmit.setStatus(QuestionSubmitStatusEnum.WAITING.getValue());questionSubmit.setJudgeInfo("{}");boolean save = this.save(questionSubmit);if (!save){throw new BusinessException(ErrorCode.SYSTEM_ERROR, "数据插入失败");}// todo 执行判题服务Long questionSubmitId = questionSubmit.getId();// 执行判题服务CompletableFuture.runAsync(() -> {judgeService.doJudge(questionSubmitId);});return questionSubmitId;
}
这边懒加载
可以解决循环依赖

相关文章:
OJ在线评测系统 后端 用策略模式优化判题机架构
判题机架构优化(策略模式) 思考 我们的判题策略可能会有很多种 比如 我们的代码沙箱本身执行程序需要消耗时间 这个时间可能不同的编程语言是不同的 比如沙箱执行Java要额外花费2秒 我们可以采用策略模式 针对不同的情况 定义不同独立的策略 而不是把所有情况全部放在一个i…...
element ui 精确控制日期控件 date-picker
https://github.com/element-plus/element-plus/discussions/17378 -- 某组件 xxx.vue ... <el-date-pickerv-model"timeRange"type"daterange"range-separator"-"start-placeholder"开始日期"end-placeholder"结束日期"…...
centos7安装指定版本php及扩展
安装EPEL仓库(如果尚未安装) sudo yum install epel-release导入REMI仓库的公钥: sudo rpm --import http://rpms.remirepo.net/RPM-GPG-KEY-remi启用REMI仓库(你可以选择PHP 7.0或者7.4,以下以7.0为例)&am…...
后端-对表格数据进行添加、删除和修改
一、添加 要求: 按下添加按钮出现一个板块输入添加的数据信息,点击板块的添加按钮,添加;点击取消,板块消失。 实现: 1.首先,设计页面输入框格式,表格首行 2.从数据库里调数据 3.添加…...
【学习笔记】手写 Tomcat 七
目录 一、优化 Dao 1. 设置 UserDaoImpl 为单例模式 2. 创建 Dao 工厂 3. 在 Service 层获取 UserDao 的实例 二、优化 Service 1. 设置 UserServiceImpl 为单例模式 2. 创建 Service 工厂 3. 在 Servlet 层获取 Service 实现类的对象 三、优化 Servlet 1. 使用配置…...
QT开发:详解 Qt 多线程编程核心类 QThread:基本概念与使用方法
1. 引言 在现代应用程序开发中,多线程编程是一个关键技术,能够显著提高程序的效率和响应速度。Qt 是一个跨平台的 C 框架,其中 QThread 类是实现多线程编程的核心类。本文将深入详解 QThread 的基本概念、使用方法及其在实际应用中的重要性。…...
【芋道源码】gitee很火的开源项目pig——后台管理快速开发框架使用笔记(微服务版之本地开发环境篇)
后台管理快速开发框架使用笔记(微服务版之本地开发环境篇) 后台管理快速开发框架使用笔记(微服务版之本地开发环境篇) 后台管理快速开发框架使用笔记(微服务版之本地开发环境篇)前言一、如何获取项目&#…...
设计模式、系统设计 record part01
技术路线: 工程师》设计师》分析师》架构师 管理路线: 项目经理》技术经理 工程师: 编程技术、测试技术 设计师: 工程师设计技术 分析师: 设计师分析技术 架构师: 分析师架构技术 项目经理: 时间…...
服务器与普通电脑的区别是什么?
服务器作为企业进行线上业务所使用的网络设备,大多数的用户对于服务器都有一定的了解,而普通的电脑则是人们在进行日常娱乐活动中经常会用到的设备,本文就来探讨一下服务器与普通电脑之间的区别是什么吧! 普通的电脑就是我们通常所…...
Vue3学习(六)Vue3 + ts几种写法
前言 官网提到组合式api和选项式api 选项式api其实就是vue2的写法,组合式api是vue3的新写法(组合式api可以在script中使用setup()也可以使用<script setup>,<script setup>是setup(ÿ…...
【前端】ES6:Proxy代理和Reflect对象
文章目录 1 Proxy代理1.1 get方法1.2 set方法1.3 has方法1.4 this问题 2 Reflect对象2.1 代替Object的某些方法2.2 修改某些Object方法返回结果2.3 命令式变为函数行为2.4 配合Proxy 1 Proxy代理 Proxy如其名,它的作用是在对象和和对象的属性值之间设置一个代理&am…...
基于微信开发助手企鹅音乐微信小程序的设计与实现(源码+文档+讲解)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
学习Spring Boot,应该从哪里开始学起
文章目录 前言1. Java基础2. Spring框架基础3. Spring Boot入门4. 搭建Spring Boot项目5. 编写RESTful API6. 数据库操作7. 安全性和测试8. 部署和运维9. 实践和项目总结前言 学习Spring Boot,应该从哪里开始学起 学习Spring Boot,你可以从以下几个步骤开始学起: 1. Java基…...
【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题
前言: 🌈上期博客:【后端开发】JavaEE初阶—线程安全问题与加锁原理(超详解)-CSDN博客 🔥感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 ⭐️小编会在后端开发的学习中不断更新~~~ &#…...
企微群管理软件:构建高效社群运营的新引擎
在数字化营销日益盛行的今天,企业微信(简称“企微”)群作为企业与用户直接互动的重要平台,其管理与运营效率直接关系到企业的品牌形象、用户满意度及市场影响力。企微群管理软件,作为专为企微社群设计的高效管理工具&a…...
CORE 中间件、wwwroot
ASP.NET Core中间件组件是被组装到应用程序管道中以处理HTTP请求和响应的软件组件(从技术上来说,组件只是C#类)。 ASP.NET Core应用程序中的每个中间件组件都执行以下任务。 选择是否将 HTTP 请求传递给管道中的下一个组件。这可…...
SpringBoot 与 Maven 快速上手指南
SpringBoot 与 Maven 快速上手指南 在Java开发领域,Spring Boot和Maven是两个极其重要的工具,它们极大地简化了企业级应用的开发和构建过程。Spring Boot通过自动配置和起步依赖等特性,让开发者能够快速搭建起一个Spring应用;而M…...
大觅网之自动化部署(Automated Deployment of Da Mi Network)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...
【C++】入门基础知识-1
🍬个人主页:Yanni.— 🌈数据结构:Data Structure. 🎂C语言笔记:C Language Notes 🏀OJ题分享: Topic Sharing 目录 前言: C关键字 命名空间 命名空间介…...
Redis一些简单通用命令认识常用数据类型和编码方式认识Redis单线程模型
通用命令 get() / set() 这是Redis中两个最为核心的命令。 set插入 这里的key 和 value都是字符串,我们可以加双引号 或者单引号,或者不加。 get查找 如果查询的key值不存在,那么会返回一个 nil ,也就是代表空 在Redis中命令…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
