OJ在线评测系统 原生Java代码沙箱核心实现流程一 搭建大体框架 用stream流拿到信息

创建一个代码沙箱的实现类

JavaNativeCodeSandboxImpl

实现了CodeSandbox的接口

核心流程的事项

用程序代替人工 用程序来操作命令行 去编译执行代码

我们要用到Java进程执行管理类 Process

1.把用户的代码保存为文件

2.编译代码 得到字节码文件

3.执行代码

4.搜集整理输出结果

5.文件清理

6.错误处理 提升程序健壮性

新建目录

把用户代码保存为一个文件

我们试一试能不能在这个目录里

保存我们的代码

在项目中引用一下我们的hutool工具类

使用这个工具类我们可以直接进行文件的读写操作

因为Java原生的文件读写操作还是很复杂的

第三方工具类 可以简化第三方开发

<!--hutool 工具类-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.8</version>
</dependency>

我们把大体的框架搭建出来哦

public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
    List<String> inputList = executeCodeRequest.getInputList();
    String code = executeCodeRequest.getCode();
    String language = executeCodeRequest.getLanguage();

    String userDir =System.getProperty("user.dir");
    String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;

    // 判断全局代码目录是否存在 没有则新建
    if(!FileUtil.exist(globalCodePathName)){
        FileUtil.mkdir(globalCodePathName);
    }

    // 把用户代码隔离存放
    String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
    String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
    File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

    return null;
}

搭建出来后 我们创建一个主类

模拟一下启动流程

public static void main(String[] args) {
    JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();

    // 搭建请求体
    ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
    executeCodeRequest.setInputList(Arrays.asList("1 2","1 3"));
    String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
    executeCodeRequest.setCode(code);
    executeCodeRequest.setLanguage("java");

    // 把请求体反馈给接口 得到响应
    ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);

    // 输出查看
    System.out.println(executeCodeRequest);
}

自动生成文件

这样就是成功的 代表我们已经拿到了代码

接下来我们把打印输出注释掉 我们要编译java代码了

java执行程序

java获取控制台的输出

接下来运行程序 获得状态码 根据状态码输出信息

// 编译代码 得到class文件
String compileCmd =   String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
try {
    Process compileProcess= Runtime.getRuntime().exec(compileCmd);
    // 等待程序执行 获取错误码
    int exitValue = compileProcess.waitFor();
    if (exitValue == 0){
        // 正常退出
        System.out.println("编译成功");
    }else {
        // 异常退出
        System.out.println("编译失败"+ exitValue );
    }
} catch (IOException | InterruptedException e) {
    throw new RuntimeException(e);
}

控制台输出

查看生成的文件

怎么拿到编译后 控制台输出的信息呢

会有一点麻烦 从进程中获取流 以成块分批的方式去读取

像这样只打印状态码的 我们也不知道到底内部是什么 这样就形成了一个程序黑盒...

改写代码

用stream流

// 编译代码 得到class文件
String compileCmd =   String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
try {
    Process compileProcess= Runtime.getRuntime().exec(compileCmd);
    // 等待程序执行 获取错误码
    int exitValue = compileProcess.waitFor();
    if (exitValue == 0){
        // 正常退出
        System.out.println("编译成功");
        // 分批获取进程的正常输出
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));
        // ACM模式 逐行读取
        String compileOutputLine ;
        while ( (compileOutputLine = bufferedReader.readLine()) !=null){
            System.out.println(compileOutputLine);
        }
    }else {
        // 异常退出
        System.out.println("编译失败 错误码: "+ exitValue );
        // 分批获取进程的异常输出
        BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getErrorStream()));
        // ACM模式 逐行读取
        String errorCompileOutputLine ;
        while ( (errorCompileOutputLine = errorBufferedReader.readLine()) !=null){
            System.out.println(errorCompileOutputLine);
        }
    }
} catch (IOException | InterruptedException e) {
    throw new RuntimeException(e);
}

用acm模式读取流 拼接字符串 然后输出

整体逻辑是通过exitValue判断程序是否正常返回 从inputStream和errorStream获取控制台输出

package work.bigdata1421.dduojcodesandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import work.bigdata1421.dduojcodesandbox.model.ExecuteCodeRequest;
import work.bigdata1421.dduojcodesandbox.model.ExecuteCodeResponse;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

public class JavaNativeCodeSandbox implements CodeSandbox{

    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";

    public static void main(String[] args) {
        JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();

        // 搭建请求体
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2","1 3"));
        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");

        // 把请求体反馈给接口 得到响应
        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);

        // 输出查看 是否拿到了代码
        System.out.println(executeCodeRequest);


    }

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        String userDir =System.getProperty("user.dir");
        String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;

        // 判断全局代码目录是否存在 没有则新建
        if(!FileUtil.exist(globalCodePathName)){
            FileUtil.mkdir(globalCodePathName);
        }

        // 把用户代码隔离存放
        String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
        String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

        // 编译代码 得到class文件
        String compileCmd =   String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
        try {
            Process compileProcess= Runtime.getRuntime().exec(compileCmd);
            // 等待程序执行 获取错误码
            int exitValue = compileProcess.waitFor();
            if (exitValue == 0){
                // 正常退出
                System.out.println("编译成功");
                // 分批获取进程的正常输出
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));
                StringBuilder compileOutputStringBuilder = new StringBuilder();
                // ACM模式 逐行读取
                String compileOutputLine ;
                while ( (compileOutputLine = bufferedReader.readLine()) !=null){
                    compileOutputStringBuilder.append(compileOutputLine);
                }
                System.out.println(compileOutputStringBuilder);
            }else {
                // 异常退出
                System.out.println("编译失败 错误码: "+ exitValue );
                // 分批获取进程的异常输出
                BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getErrorStream()));
                StringBuilder errorCompileOutputStringBuilder =new StringBuilder();
                // ACM模式 逐行读取
                String errorCompileOutputLine ;
                while ( (errorCompileOutputLine = errorBufferedReader.readLine()) !=null){
                    errorCompileOutputStringBuilder.append(errorCompileOutputLine);
                }
                System.out.println(errorCompileOutputStringBuilder);
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值