在线OJ项目核心思路

news2025/2/26 19:36:14

文章目录

  • 在线OJ项目核心思路
    • 1. 项目介绍
    • 2.预备知识
      • 理解多进程编程
      • 为啥采用多进程而不使用多线程?
      • 标准输入&标准输出&标准错误
    • 3.项目实现
      • 题目API实现
        • 相关实体类定义
        • 新增/修改题目
        • 获取题目列表
      • 编译运行
        • 编译运行流程
    • 4.统一功能处理


在线OJ项目核心思路

在这里插入图片描述

1. 项目介绍

该项目是一个类似于力扣的在线OJ平台,可以进行题目的编写和提交编译运行以及结果展示,使用的技术栈有:Java、MySQL、SpringBoot、MyBatis、Redis、Nginx、Docker

主要功能如下:

  1. 登录和注册(Session持久化+密码加盐)
  2. 图形验证码验证登录
  3. 题目管理(题目的添加和修改)
  4. 题目提交(编译+运行)
  5. 题目编译/运行结果展示
  6. Nginx+Docker实现负载均衡

在这里插入图片描述

2.预备知识

理解多进程编程

什么是进程?

进程可以看做操作系统中一个正在运行的程序的一个抽象,也可以把进程看做是程序的一次运行过程。在操作系统内部,进程是操作系统进行资源分配的基本单位

  • 使用 PCB(进程控制块) 描述进程

  • 组织:使用一定的数据结构来组织,常见做法就是使用双向链表

  • 进程之间是相互独立的

什么是多进程?

一个CPU运行多个进程

由于CPU的运行速度极快,虽然CPU在一直进行切换,但是咱们坐在电脑前的用户,是感知不到这个切换过程的

进程和线程的关系

  1. 进程是包含线程的,一个进程里可以有一个线程,也可以有多个线程
  2. 每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用这个虚拟地址空间
  3. 进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位
  4. 如果一个进程挂了, 不会影响到其他进程. 如果一个线程挂了, 则整个进程都要异常终止.
  5. 进程更重量, 线程更轻量. 创建/销毁/调度线程比进程更高效.

Java中的多进程编程

Java中中对系统提供的进程创建、进程终止、进程程序替换、进程间通信进程了限制,最终只给用户提供了两个操作

进程的创建

创建出一个新的进程,让这个新的进程来执行一系列任务,被创建出来的进程,称为"子进程",创建子进程的进程,称为"父进程",服务器的进程就相当于一个父进程

根据收到的用户发送过来的代码再 创建出一个子进程,一个父进程,可以有多个子进程,但是一个子进程,只能有一个父进程

为啥采用多进程而不使用多线程?

一个操作系统上是运行了很多进程的,因为进程之间是相互隔离的,一个进程挂了是不会影响到其它进程的。如果使用多线程,我们并不知道用户提交的会提交什么样的代码,很可能提交一些恶意代码导致线程崩溃,而线程挂了很有可能就影响到了我们的整个服务进程。所以一定要采用多进程而不是多线程。

标准输入&标准输出&标准错误

java和javac是一个控制台程序,它的输出,是输出到“标准输出”和"标准错误"这两个特殊的文件当中的,一个进程启动的时候,就会自动打开三个文件:

  1. 标准输入,对应到键盘
  2. 标准输出,对应到显示器
  3. 标椎错误,对应到显示器

Runtime是Java中内置的一个单例类

  • 通过runtime.exec方法参数是一个字符串,表示一个可执行程序的路径,执行这个方法就会把指定路径的可执行程序,创建出一个子进程并执行。
  • runtime.exec()方法返回的是一个Process类,表示的就是一个子进程,后续通过这个子进程来进行操作
    • 获取标准输入:process.getInputStream():该方法能把process这个子进程的标准输出给读取出来
    • 获取标准错误:process.getErrorStream():该方法能把process这个子进程的标准错误给读取出来
    • 进程等待: process.waitFor():该方法能能让主进程进行阻塞等待,等待子进程process执行完毕。

3.项目实现

题目API实现

相关实体类定义

题目实体类

public class Problem {
    private Integer id;
    private String title;
    private String levels;
    private String description;
    private String templateCode;
    private String testCode;
    private Date createTime;
    private Date updateTime;
}
新增/修改题目

新增修改题目通过判断url中的querystr里是否存在题目Id,来判断是修改题目还是新增题目

约定请求:

post
{
    "id" : "",
    "title" : "题目标题",
    "levels" : "题目难度",
    "description" : "题干",
    "templateCode" : "题目代码模板",
    "testCode" :  "题目测试用例"
}

响应:

{
    code : 200,
    message : ""
    data: 
}
@PostMapping("/add")
public Response add(@RequestBody Problem problem) {
    if (problem == null || problem.getTitle() == null || "".equals(problem.getTitle().trim()) || problem.getLevels() == null ||
        "".equals(problem.getLevels().trim()) || problem.getTestCode() == null || "".equals(problem.getTestCode().trim()) ||
        problem.getTemplateCode() == null || "".equals(problem.getTemplateCode().trim())) {
        return Response.fail("题目参数不完整");
    }
    int ret = problemService.add(problem);
    if (ret == 1) {
        return Response.success(200,"添加成功");
    }
    return Response.fail("添加失败");
}
获取题目列表

请求:

post
{
    /problem/all
}

响应:

{
    code : 200,
    message:"",
    data:
    [
        {
            id : 1,
            title: "两数之和",
            levels: "简单",
            description: "题干",
            template: "题目模板"
        }
    ]
}

编译运行

通过Answer表示编译运行结果,约定:

  • 错误码为0表示运行成功
  • 错误码为1表示编译错误
  • 错误码为2表示运行错误
  • 错误码为1表示提交了违规代码
public class Answer {
    // 错误码 0表示运行成功,1表示编译错误,2表示运行错误,-1表示违规代码
    private Integer errorCode;
    // 标准输出
    private String stdout;
    // 错误信息
    private String errorInfo;
}

Task类描述的是每一次代码的提交:

通过UUID生成唯一的目录,保证每个用户提交的代码相互隔离

public class Task {

    // 存放临时文件目录
    private String workDir;
    // 运行文件路径
    private String className;
    // 编译文件路径
    private String classFile;
    // 存放编译错误信息文件
    private String compileErrorFile;
    // 标准输出文件
    private String stdoutFile;
    // 标准错误文件
    private String stderrFile;

    public Task() {
        this.workDir = "./tmp/"+UUID.randomUUID().toString()+"/";
        this.className = "Solution";
        this.classFile = workDir+ "Solution.java";
        this.compileErrorFile = workDir+"compileErrInfo.txt";
        this.stdoutFile = workDir+"stdout.txt";
        this.stderrFile = workDir+"stderr.txt";
    }
}
编译运行流程

请求:

{
    problemId : "题目id",
    code : "提交的代码"
}

响应:

{
    code : 200,
    message : "信息",
    data:{
        errorCode : "错误码",
        stdout: "标准输出",
        derrorInfo, "出错信息"
    }
}

编译运行流程:

  1. 对用户提交代码进行判空
  2. 从数据库中查询出测试用例进和提交代码进行拼接,形成完整代码。
  3. 对用户提交代码进行安全校验,判断其是否提交操作系统命令、文件网络等危险操作代码
  4. 把拼接好的代码写入到对应文件
  5. 进行编译和运行。

如下方法表示一次编译或者运行:

  • 通过判断stdoutFile是否为空来判断是编译还是运行
  • 从子进程process中的标准错误流中读取数据写入到task类的唯一的编译错误信息文件中,再判断文件内容是否为空
  • 如果编译错误信息文件不为空,说明编译出错直接返回
  • 如果编译错误信息文件为空,说明编译正确,再对编译后的字节码进行运行
  • 运行后再进行判断标准错误信息文件是否为空,如果为空说明运行正常,读取到标准输入文件里的信息返回给用户
/**
     * 编译运行
     * @param cmd 执行的命令
     * @param stdoutFile
     * @param stderrFile
     * @return
     */
public static int run(String cmd,String stdoutFile,String stderrFile) {
    Runtime runtime =  Runtime.getRuntime();
    int exitCode = -1;
    try {
        // 执行命令获得子进程
        Process process = runtime.exec(cmd);
        // 编译
        if (stdoutFile == null) {
            try (InputStream stderrInoutStream = process.getErrorStream();OutputStream stderrOutputSteam = new FileOutputStream(stderrFile);){

                int ch;
                // 将错误信息读入到错误日志文件
                while ((ch = stderrInoutStream.read()) != -1) {
                    stderrOutputSteam.write(ch);
                }
            }

        }
        // 说明是运行
        if (stdoutFile != null) {
            try (InputStream stderrInoutStream = process.getErrorStream();
                 OutputStream stderrOutputSteam = new FileOutputStream(stderrFile);
                 InputStream stdoutInputStream = process.getInputStream();
                 OutputStream stdOutputStream = new FileOutputStream(stdoutFile)){
                // 获取标准错误输入流

                int ch;
                // 将错误信息读入到错误日志文件
                while ((ch = stderrInoutStream.read()) != -1) {
                    stderrOutputSteam.write(ch);
                }
                // 将子进程标准输出写入到指定文件
                while ((ch = stdoutInputStream.read()) != -1) {
                    stdOutputStream.write(ch);

                }
            }
        }
        // 进程等待
        exitCode = process.waitFor();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    return exitCode;
}

拼接编译命令时通过 -d指定编译后的文件存放到指定位置,不然找不到字节码文件位置。

// 2.拼接编译命令
String compileCmd = String.format("javac -encoding utf8 %s -d %s",classFile,workDir);
//4.运行代码
String runCmd = String.format("java -classpath %s %s",workDir,className);

4.统一功能处理

统一登录拦截

定义拦截器:

  1. 创建自定义拦截器,实现Handlerlnterceptor接口的preHandle(执行具体方法之前的预处理)方法
  2. 将自定义拦截器加入WebMvcConfigureraddInterceptors

提供一个管理员页面来对题目进行添加和修改。管理员页面使用拦截器对普通用户进行拦截.

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/verificationCode")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/img/**");
        registry.addInterceptor(new AdminInterceptor())
                .addPathPatterns("/admin.html")
                .addPathPatterns("/addProblem.html")
                .addPathPatterns("/problem/update")
                .addPathPatterns("/problem/add");
    }
}

统一格式返回

统一的数据返回格式使用@ControllerAdvice+ResponseBodyAdvice实现

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Resource
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Response) {
            return body;
        }
        if (body instanceof  String) {
            try {
                return objectMapper.writeValueAsString(body);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return Response.success(body);
    }
}

统一异常处理

@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Response exceptionAdvice(Exception e) {
        return Response.fail("服务器异常");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1057914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【每日一题】买卖股票的最佳时机 III

文章目录 Tag题目来源题目解读解题思路方法一:动态规划 写在最后 Tag 【动态规划】【数组】【2023-10-03】 题目来源 123. 买卖股票的最佳时机 III 题目解读 有一个表示股票价格的数组,你需要计算出在最多可以完成两笔交易的前提下可获得的最大收益&a…

什么样的枕头可以让睡眠更舒适——四个月的反复试验结果

如何提高睡眠质量,我们先从睡眠中的呼吸质量谈起,这里面有大量的数据和记录,我后续会整理我这七八年来积累的所有睡眠质量数据进行分析汇总和处理。 几个月前我在看我的华为手表监控的睡眠数据时看到了关于睡眠中呼吸质量的数据,最…

1.6 计算机网络的性能

思维导图: 1.6.1 计算机网络的性能指标 前言: 我的理解: 这段前言主要介绍了关于计算机网络性能的两个方面的讨论。首先,计算机网络的性能可以通过一些重要的性能指标来衡量。但除了这些指标之外,还有一些非性能特征…

【强化算法专题一】双指针算法

【强化算法专题一】双指针算法 1.双指针算法--移动零2.双指针算法--复写零3.双指针算法--快乐数4.双指针算法--盛水最多的容器5.双指针算法--有效三角形的个数6.双指针算法--和为s的两个数7.双指针算法--三数之和8.双指针算法--四数之和 1.双指针算法–移动零 算法原理解析----…

BIT-6自定义类型和动态内存管理(11000字详解)

一:自定义类型 1.1:结构体 在生活中,基本数据类型可以描述绝大多数的物体,比如说名字,身高,体重,但是还有一部分物体还不足够被描述,比如说我们该如何完整的描述一本书呢&#xff…

VSCode安装图文详解教程

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl 教程说明 本教程旨在详细介绍VSCode的安装过程及其注意事项。 下载VSCode 请在官方网站 https://code.visualstudio.com/ 下载https://code.visualstudio.com/至本地&…

Android学习之路(18) 数据存储与访问

文件存储读写 1.Android文件的操作模式 学过Java的同学都知道,我们新建文件,然后就可以写入数据了,但是Android却不一样,因为Android是 基于Linux的,我们在读写文件的时候,还需加上文件的操作模式&#x…

设计模式之适配器模式:接口对接丝般顺滑(图代码解析面面俱到)

目录 概要概念组成类图工作原理应用场景优点 类型类适配器模式对象适配器模式两者区别示例代码 实现(对象适配器详解)业务背景代码 常见问题为什么有适配器模式适配器模式告诉我们什么适配器模式体现了哪些设计原则关联方式实现了逻辑继承适配器模式在Sp…

春招秋招,在线测评应用得越来越普及

这年代提到测评,很多人都比较熟悉,它有一种根据所选的问题给予合适答案方面的作用。因为不同的测评带来的影响不一样,所以很多人都会关注在线测评的内容有哪些。在校园招聘上面,在线测评也频繁出现了,这让很多人好奇它…

VD6283TX环境光传感器驱动开发(2)----获取光强和色温

VD6283TX环境光传感器驱动开发----1.获取光强和色温 概述视频教学样品申请源码下载参考源码设置增益基准配置设置ALS曝光时间通道使能启用ALS操作中断查询及清除获取ALS数据计算光强及色温结果演示 概述 为了更好地利用VD6283TX传感器的特点和功能,本章专门用于捕获…

用通俗易懂的方式讲解大模型分布式训练并行技术:张量并行

近年来,随着Transformer、MOE架构的提出,使得深度学习模型轻松突破上万亿规模参数,传统的单机单卡模式已经无法满足超大模型进行训练的要求。因此,我们需要基于单机多卡、甚至是多机多卡进行分布式大模型的训练。 而利用AI集群&a…

最近脑机接口突破性成果这么多,它到底走到哪一步了?

美国心脏协会(AHA)首席临床科学官、哥伦比亚大学神经病学和流行病学终身教授Mitchell Elkind在接受NeuroNews采访时概述了脑机接口(BCI)技术的巨大潜力:“恢复患者活动能力的可能性可能会带来巨大的好处。”“对于那些功能受限的人来说,即使是微小的进步也能改变他们…

【数仓精品理论分析】能不能学大数据?

【数仓精品理论分析】能不能学大数据? 还能不能学大数据datapulse官网: 自身情况数据行业发展情况 还能不能学大数据 首先看到这个话题的时候,我是这样想的,能不能学大数据需要参考本人的自身情况【学历、年龄、决心、有没有矿或者…

高層建築設計和建造:從避難層到設備間和防風防火防水的設計理念,酒店住宅辦公樓都有什麽房間(精簡)

樓層概覽 標準層居住、辦公、商業等功能的樓層。結構和裝修與其他樓層相同,可供人正常居住、工作和活動避難層專門用於人員避難的樓層,通常會相隔數十個標準層,樓梯通常和標準層是錯開的(非公用),具有更多的通風口。牆體和樓板具…

黑豹程序员-架构师学习路线图-百科:CSS-网页三剑客

文章目录 1、为什么需要CSS2、发展历史3、什么是CSS4、什么是SASS、SCSS 1、为什么需要CSS 作为网页三剑客的第二,CSS为何需要它,非常简单HTML只能完成页面的展现,但其做出来的页面奇丑无比。 随着网络的普及,人们的要求更高&…

Ubantu 20.04 卸载与安装 MySQL 5.7 详细教程

文章目录 卸载 MySQL安装 MySQL 5.71.获取安装包2.解压并安装依赖包3.安装 MySQL4.启动 MySQL 扩展开启 gtid 与 binlog 卸载 MySQL 执行以下命令即可一键卸载,包括配置文件目录等。 # 安装sudo软件 apt-get install sudo -y # 卸载所有以"mysql-"开头的…

小病变检测:Gravity Network for end-to-end small lesion detection

论文作者:Ciro Russo,Alessandro Bria,Claudio Marrocco 作者单位:University of Cassino and L.M. 论文链接:http://arxiv.org/abs/2309.12876v1 内容简介: 1)方向:医学影像中小病变检测 2&#xff0…

PyQt5+Qt设计师初探

在上一篇文章中我们搭建好了PyQt5的开发环境,打铁到趁热我们基于搭建好的环境来简单实战一把 一:PyQt5包模块简介 PyQt5包括的主要模块如下。 QtCore模块——涵盖了包的核心的非GUI功能,此模块被用于处理程序中涉及的时间、文件、目录、数…

代谢组学最常用到的数据分析方法(五)

代谢组学是一门对某一生物或细胞所有低分子质量代谢产物&#xff08;以相对分子质量<1000的有机和无机的代谢物为研究核心区&#xff09;进行分析的新兴学科。因此从复杂的代谢组学数据中确定与所研究的现象有关的代谢物&#xff0c;筛选出候选生物标记物成为代谢物组学研究…

秋招,网申测评,认知能力测试

随着秋季的到来&#xff0c;越来越多的企业开始进行职业招聘&#xff0c;在这些招聘中&#xff0c;我们总会看到“认知能力测试”的影子。说到这个测试&#xff0c;很多人可能还不太理解&#xff0c;不知道这种测试是什么。 1、什么是认知能力测试 认知能力是指大脑加工、…