Java 原生实现代码沙箱(OJ判题系统第1期)——设计思路、实现步骤、代码实现

发布于:2025-05-10 ⋅ 阅读:(21) ⋅ 点赞:(0)

设计思路:


1、保存代码文件

✅ 目的:
将用户提交的源码以字符串形式写入磁盘,生成 .java 文件。

📌 原因:
Java 是静态语言,必须先编译成 .class 文件才能运行。
需要物理文件路径来调用 javac 或使用 JavaCompiler API 编译。
可以通过隔离目录结构(如 UUID 子目录)实现用户代码之间的隔离。

实现步骤:

  1. 获取当前工作目录路径

    • 使用 System.getProperty("user.dir") 获取当前运行程序的工作目录路径。例如:/home/user/project
  2. 构建全局代码存储目录的完整路径

    • 将工作目录路径与全局代码存储目录名称(假设是一个预定义的常量 GLOBAL_CODE_DIR_NAME)拼接起来,形成全局代码存储目录的完整路径。例如:/home/user/project/codeTemp
  3. 检查并创建全局代码存储目录

    • 使用工具类 FileUtil.exist(globalCodePathName) 判断全局代码存储目录是否存在。如果不存在,则调用 FileUtil.mkdir(globalCodePathName) 创建该目录。
  4. 生成用户专属目录

    • 通过 UUID.randomUUID() 生成一个唯一标识符,并将其作为当前用户的代码存放目录名,目的是为了避免多个用户提交代码时发生文件覆盖的问题。例如:/home/user/project/codeTemp/uuid-xxxxxx
  5. 构造具体的Java文件路径

    • 在用户专属目录下创建一个固定名称的 Java 文件(假设使用的是 GLOBAL_JAVA_CLASS_NAME 作为文件名),例如:/home/user/project/codeTemp/uuid-xxxxxx/Main.java
  6. 将用户代码写入到指定路径的文件中

    • 使用 FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8) 方法将用户传入的代码字符串以 UTF-8 编码格式写入到之前构造好的文件路径中。这个方法还会自动创建文件及其父目录(如果它们还不存在的话)。
  7. 返回创建好的文件对象

    • 最后,返回刚刚创建好的 File 对象,供调用者用于后续的编译或执行操作。

 

 

/**
 * 1. 把用户的代码保存为文件
 *
 * 这个方法的主要目的是将用户传入的字符串形式的 Java 源代码保存为一个临时文件,
 * 并返回该文件对象。为了隔离不同用户的代码,每次都会创建一个独立的目录来存放。
 *
 * @param code 用户输入的 Java 源代码字符串
 * @return 返回保存后的 Java 文件对象(File),可用于后续编译或执行
 */
public File saveCodeToFile(String code) {
    // 获取当前运行程序的工作目录路径(例如:/home/user/project)
    String userDir = System.getProperty("user.dir");

    // 构建全局代码存储目录的完整路径(userDir + 文件分隔符 + 全局目录名)
    // 例如:/home/user/project/codeTemp
    String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;

    // 判断这个全局代码目录是否存在
    // 如果不存在,则使用工具类 FileUtil 创建该目录
    if (!FileUtil.exist(globalCodePathName)) {
        FileUtil.mkdir(globalCodePathName); // 创建目录
    }

    // 生成一个唯一标识符作为当前用户的代码目录名(UUID.randomUUID())
    // 目的是为了避免多个用户提交代码时发生文件覆盖的问题
    String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();

    // 构造具体的 Java 文件路径(在用户专属目录下创建一个固定名称的 Java 文件)
    // 例如:/home/user/project/codeTemp/uuid-xxxxxx/Main.java
    String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;

    // 使用 FileUtil 工具类将用户传入的代码字符串写入到指定路径的文件中
    // 编码方式为 UTF-8,确保中文等字符不会乱码
    // writeString 方法会自动创建文件及其父目录(如果不存在)
    File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

    // 返回创建好的文件对象,供调用者使用(如用于后续编译、执行操作)
    return userCodeFile;
}

2. 编译代码,得到 class 文件

✅ 目的:

.java 源文件编译为 .class 字节码文件,供 JVM 执行。

📌 原因:
  • Java 程序不能直接执行 .java 文件,必须先经过编译。
  • 使用标准工具(如 javac 或 Java Compiler API)进行编译。
  • 如果编译失败,需要捕获错误信息并反馈给用户。

实现步骤: 

  1. 构造编译命令

    • 使用 String.format() 方法构建用于编译 Java 源代码文件的命令字符串。这里使用了 javac 命令,并通过 -encoding utf-8 参数指定源文件的字符编码格式为 UTF-8,以确保支持中文等非ASCII字符集。
    • 获取用户代码文件的绝对路径(userCodeFile.getAbsolutePath()),将其拼接到命令字符串中。
  2. 执行编译命令

    • 使用 Runtime.getRuntime().exec(compileCmd) 方法执行上述构造好的编译命令。这会启动一个子进程运行 javac 编译器来编译用户的 Java 源代码文件。
    • 返回的 Process 对象表示正在运行的编译进程,可以通过它获取编译过程中的输入输出流以及控制进程的行为。
  3. 处理编译过程和结果

    • 调用自定义工具类 ProcessUtils.runProcessAndGetMessage(compileProcess, "编译") 来处理编译进程。这个方法通常负责:
      • 读取并收集编译过程中的标准输出和错误输出信息。
      • 等待编译进程结束并获取其退出状态码。
      • 将上述信息封装到一个 ExecuteMessage 对象中返回。
  4. 检查编译是否成功

    • 根据 ExecuteMessage 对象中的退出值(exitValue)判断编译是否成功。如果退出值不为0(即 executeMessage.getExitValue() != 0),则认为编译失败,并抛出一个 RuntimeException 异常,附带消息“编译错误”。
  5. 异常处理

    • 如果在执行编译命令或处理编译过程中发生任何异常(如 IO 异常、编译命令执行失败等),则捕获这些异常并在当前实现中直接抛出一个新的 RuntimeException,将原始异常作为其原因。注意,这里的异常处理策略可以根据实际需要调整,例如可以返回一个包含错误信息的响应对象而不是直接抛出异常。
  6. 返回编译结果

    • 如果编译成功(即退出值为0),则返回封装了编译过程信息的 ExecuteMessage 对象给调用者。该对象包含了编译的标准输出、错误输出及退出状态码,供后续逻辑使用。
/**
 * 进程执行信息
 */
@Data
public class ExecuteMessage {

    private Integer exitValue;

    private String message;

    private String errorMessage;

    private Long time;

    private Long memory;
}

 

/**
 * 2. 编译代码
 *
 * 此方法用于将用户保存的 Java 源代码文件(.java)进行编译,
 * 生成对应的字节码文件(.class)。如果编译失败,会记录错误信息。
 *
 * @param userCodeFile 用户的 Java 源代码文件对象(已保存到磁盘)
 * @return 返回一个 ExecuteMessage 对象,包含编译过程的标准输出、错误输出和退出码
 */
public ExecuteMessage compileFile(File userCodeFile) {
    // 构造编译命令:javac -encoding utf-8 [源文件路径]
    // -encoding utf-8 确保支持中文等字符集
    String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());

    try {
        // 使用 Runtime.getRuntime().exec() 执行系统命令 javac 进行编译
        // 返回一个 Process 对象,表示正在运行的编译进程
        Process compileProcess = Runtime.getRuntime().exec(compileCmd);

        // 调用工具类 ProcessUtils.runProcessAndGetMessage() 来运行并监听编译过程
        // 该方法会:
        //   - 读取标准输出流(System.out)
        //   - 读取错误输出流(System.err)
        //   - 获取进程退出码(exit code)
        // 最终封装成一个 ExecuteMessage 对象返回
        ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");

        // 判断编译是否成功:
        // - 如果 exitValue == 0,说明编译通过
        // - 如果 exitValue != 0,说明有语法错误或其他问题
        if (executeMessage.getExitValue() != 0) {
            // 抛出运行时异常,并提示“编译错误”
            // 实际项目中可以改为更具体的异常类型或封装错误信息返回
            throw new RuntimeException("编译错误");
        }

        // 如果编译成功,返回编译结果的信息对象
        return executeMessage;

    } catch (Exception e) {
        // 捕获所有异常,包括 IO 异常、执行命令失败、中断等
        // 可以选择记录日志或返回错误响应对象(如 getErrorResponse(e))
        // 当前直接抛出运行时异常

        // 注意:这里可以选择不抛出异常,而是返回 ExecuteMessage 错误对象
        // 示例:return getErrorResponse(e);
        throw new RuntimeException(e);
    }
}

3. 执行代码,得到输出结果

✅ 目的:

在受控环境下运行用户代码,并捕获其输出(包括标准输出和错误输出)。

📌 原因:
  • 用户代码可能包含无限循环、异常抛出、资源占用等风险行为。
  • 需要限制执行时间、内存使用,防止系统崩溃或被攻击。
  • 需要重定向 System.out 和 System.err 来获取输出内容。

实现步骤:

 

  1. 获取用户代码文件的父目录路径

    • 使用 userCodeFile.getParentFile().getAbsolutePath() 获取用户代码文件所在目录的绝对路径。因为编译后的 .class 文件与 .java 文件位于同一目录下,所以这个路径可以用来指定 Java 运行时的类路径(-cp 参数)。
  2. 初始化执行结果列表

    • 创建一个 ArrayList<ExecuteMessage> 类型的列表 executeMessageList 用于存储每次运行的结果信息。每个 ExecuteMessage 对象包含一次执行的标准输出、错误输出以及退出状态码。
  3. 遍历输入参数列表

    • 使用 for (String inputArgs : inputList) 循环遍历传入的 inputList,其中每个元素代表一组测试用例的输入参数。
  4. 构造运行命令

    • 使用 String.format() 方法构建用于运行 Java 程序的命令字符串。该命令包括以下部分:
      • -Xmx256m:设置 JVM 最大堆内存为 256MB,以防止程序占用过多内存。
      • -Dfile.encoding=UTF-8:设置文件编码格式为 UTF-8,确保支持多语言字符集。
      • -cp %s:指定类路径为当前用户的代码目录。
      • Main:要执行的主类名(假设是 Main.class)。
      • %s:本次循环的输入参数,作为 main 方法的参数传入。
  5. 执行命令并启动超时监控

    • 使用 Runtime.getRuntime().exec(runCmd) 执行上述构造好的命令,启动一个子进程来运行 Java 程序。
    • 启动一个新的线程,使用 Thread.sleep(TIME_OUT) 来实现超时控制。如果超过设定的时间限制,则调用 runProcess.destroy() 强制终止该进程,避免长时间占用资源或死循环等情况。
  6. 处理运行过程和结果

    • 调用 ProcessUtils.runProcessAndGetMessage(runProcess, "运行") 方法处理运行进程。此方法通常会读取并收集标准输出和错误输出信息,并等待进程结束获取其退出状态码。
    • 将收集到的信息封装成一个 ExecuteMessage 对象,并将其添加到 executeMessageList 中。
  7. 异常处理

    • 如果在执行命令或处理过程中发生任何异常(例如 IO 异常、命令执行失败等),则捕获这些异常并抛出一个新的 RuntimeException,附带原始异常作为原因。
  8. 返回执行结果列表

    • 当所有输入参数都被处理完毕后,返回包含所有执行结果的 executeMessageList 列表。
/**
 * 3. 执行文件,获得执行结果列表
 *
 * 此方法用于执行用户编译后的 Java 字节码文件(.class),并传入多组输入参数,
 * 每次运行一个测试用例,并收集输出结果。
 *
 * @param userCodeFile 编译生成的 .class 文件所在目录中的源代码文件(用来获取父路径)
 * @param inputList    用户提供的多个输入参数列表,代表多个测试用例
 * @return 返回一个 ExecuteMessage 列表,每个元素对应一次运行的标准输出、错误输出和退出码
 */
public List<ExecuteMessage> runFile(File userCodeFile, List<String> inputList) {
    
    // 获取用户代码文件所在的父目录绝对路径(即存放 .class 文件的目录)
    String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();

    // 创建一个列表,用于存储每次执行的结果信息对象
    List<ExecuteMessage> executeMessageList = new ArrayList<>();

    // 遍历输入参数列表,依次对每一组输入执行程序
    for (String inputArgs : inputList) {

        // 构造 Java 运行命令:
        // -Xmx256m: 设置 JVM 最大堆内存为 256MB,防止内存溢出
        // -Dfile.encoding=UTF-8: 强制使用 UTF-8 编码,避免中文乱码
        // -cp %s: 指定类路径为当前目录(即 userCodeParentPath)
        // Main: 要执行的主类名(假设是 Main.class)
        // %s: 本次循环的输入参数,作为 main 方法的 args 参数传入
        String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParentPath, inputArgs);

        try {
            // 使用 Runtime.getRuntime().exec() 执行构建好的 java 命令
            Process runProcess = Runtime.getRuntime().exec(runCmd);

            // 启动一个守护线程来监控执行时间,实现超时控制
            new Thread(() -> {
                try {
                    // 等待预设的超时时间(TIME_OUT,单位毫秒)
                    Thread.sleep(TIME_OUT);
                    
                    // 如果还未执行完成,则强制终止进程
                    System.out.println("超时了,中断");
                    runProcess.destroy();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }).start();

            // 使用工具类 ProcessUtils 来运行并监听进程的执行过程
            // 该方法会读取标准输出流和错误输出流,并返回封装好的 ExecuteMessage 对象
            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(runProcess, "运行");

            // 将单次执行结果添加到结果列表中
            executeMessageList.add(executeMessage);

        } catch (Exception e) {
            // 如果执行过程中出现异常(如 IO 错误、命令执行失败等),
            // 则抛出运行时异常,并附带原始异常作为原因
            throw new RuntimeException("执行错误", e);
        }
    }

    // 返回所有测试用例执行后的结果列表
    return executeMessageList;
}

4. 收集整理输出结果

✅ 目的:

将程序执行的标准输出、错误输出、退出码等信息整合后返回给调用者。

📌 原因:
  • 提供给用户清晰的运行结果反馈。
  • 包括成功输出、异常堆栈、超时提示、内存溢出警告等。
  • 用于后续判断是否通过测试用例。

实现步骤: 

  1. 初始化响应对象

    • 创建一个 ExecuteCodeResponse 对象 executeCodeResponse,用于封装最终返回给用户的响应信息。
    • 初始化一个 List<String> 类型的列表 outputList,用于存储所有成功执行的结果输出。
  2. 初始化最大执行时间

    • 定义一个 long 类型变量 maxTime 并初始化为 0,用于记录所有测试用例中最大的执行时间(毫秒),以便于后续判断是否超时或评估性能。
  3. 遍历执行消息列表

    • 使用 for (ExecuteMessage executeMessage : executeMessageList) 遍历传入的 executeMessageList,每个 executeMessage 包含一次执行的标准输出、错误输出、退出码及执行时间等信息。
  4. 检查并处理错误信息

    • 获取当前执行结果中的错误信息 errorMessage
    • 如果 errorMessage 不为空且非空白字符串(使用 StrUtil.isNotBlank(errorMessage) 检查),则表示该次执行出现了错误。
      • 将错误信息设置到 executeCodeResponse 的 message 字段中。
      • 设置 executeCodeResponse 的状态码为 3(代表代码在运行过程中出现错误)。
      • 立即跳出循环,停止进一步处理其他执行结果,因为一旦有错误发生,通常意味着整个过程失败。
  5. 收集标准输出和更新最大执行时间

    • 如果没有错误信息,则将当前执行结果的标准输出内容添加到 outputList 中。
    • 获取当前执行结果的执行时间 time(单位可能是毫秒),如果该值不为空,则更新 maxTime 为当前已知的最大值。
  6. 检查全部成功执行情况

    • 在循环结束后,比较 outputList.size() 和 executeMessageList.size()。如果两者相等,说明所有输入参数对应的执行均成功完成,此时设置 executeCodeResponse 的状态码为 1(表示全部运行成功,无任何错误)。
  7. 设置输出结果列表

    • 将收集到的所有标准输出内容 outputList 设置到 executeCodeResponse 的 outputList 字段中。
  8. 构建判题信息

    • 创建一个新的 JudgeInfo 对象 judgeInfo,用于封装判题所需的信息(如执行时间和内存占用)。
    • 将之前计算得到的最大执行时间 maxTime 设置到 judgeInfo 的 time 字段中。
    • (注:关于内存占用的获取较为复杂,通常需要借助 JVM 工具或者操作系统命令行工具,在 Java 进程中精确获取用户代码使用的内存非常困难,因此此处不做具体实现)
  9. 设置判题信息

    • 将 judgeInfo 设置到 executeCodeResponse 的 judgeInfo 字段中。
  10. 返回响应对象

    • 返回填充完毕的 executeCodeResponse 对象,供调用者使用。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExecuteCodeResponse {

    private List<String> outputList;

    /**
     * 接口信息
     */
    private String message;

    /**
     * 执行状态
     */
    private Integer status;

    /**
     * 判题信息
     */
    private JudgeInfo judgeInfo;
}

 

/**
 * 4. 获取输出结果
 *
 * 此方法根据代码执行过程中的多个执行结果(ExecuteMessage 列表),
 * 构建最终返回给用户的响应对象 ExecuteCodeResponse。
 * 主要功能包括:
 * - 提取所有运行成功的结果
 * - 检查是否有错误信息,并设置对应状态码
 * - 收集最大执行时间等判题信息
 *
 * @param executeMessageList 执行过程中收集到的多个 ExecuteMessage 对象列表
 * @return 返回封装好的 ExecuteCodeResponse 对象,包含输出、状态、判题信息等
 */
public ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> executeMessageList) {
    
    // 创建一个最终要返回的响应对象
    ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();

    // 用于存储所有成功的标准输出内容
    List<String> outputList = new ArrayList<>();

    // 用于记录所有测试用例中最大的执行时间(毫秒),便于判断是否超时
    long maxTime = 0;

    // 遍历每个 ExecuteMessage(即每次输入参数对应的执行结果)
    for (ExecuteMessage executeMessage : executeMessageList) {

        // 获取当前执行结果的错误信息(如果有)
        String errorMessage = executeMessage.getErrorMessage();

        // 如果错误信息不为空或非空白字符串,说明该次执行出错
        if (StrUtil.isNotBlank(errorMessage)) {
            // 将错误信息设置到响应对象中
            executeCodeResponse.setMessage(errorMessage);

            // 设置状态码为 3:代表代码在运行过程中出现错误(如异常、编译未通过等)
            executeCodeResponse.setStatus(3);

            // 出现错误后直接跳出循环,不再处理后续结果
            break;
        }

        // 如果没有错误,则将本次运行的标准输出加入结果列表
        outputList.add(executeMessage.getMessage());

        // 获取本次运行的时间(单位可能是毫秒)
        Long time = executeMessage.getTime();

        // 如果时间有效,则更新最大时间
        if (time != null) {
            maxTime = Math.max(maxTime, time);
        }
    }

    // 如果所有测试用例都成功运行完毕(输出数量等于执行次数)
    if (outputList.size() == executeMessageList.size()) {
        // 设置状态码为 1:表示全部运行成功,无任何错误
        executeCodeResponse.setStatus(1);
    }

    // 将收集到的标准输出结果设置到响应对象中
    executeCodeResponse.setOutputList(outputList);

    // 创建并填充 JudgeInfo 判题信息对象(如时间和内存)
    JudgeInfo judgeInfo = new JudgeInfo();
    judgeInfo.setTime(maxTime); // 设置最大运行时间

    // 内存占用获取较为复杂,通常需要借助 JVM 工具或者操作系统命令行工具(如 ps、top 等)
    // 在 Java 进程中精确获取用户代码使用的内存非常困难,因此此处不做具体实现
//    judgeInfo.setMemory();

    // 将判题信息设置到响应对象中
    executeCodeResponse.setJudgeInfo(judgeInfo);

    // 返回完整的响应对象
    return executeCodeResponse;
}

5. 文件清理,释放空间

✅ 目的:

删除临时生成的 .java.class 文件及目录,防止磁盘爆满。

📌 原因:
  • 沙箱频繁运行会导致大量临时文件堆积。
  • 不及时清理会影响服务器性能与稳定性。
  • 使用完即删是良好资源管理习惯。

实现步骤:

 

  1. 检查用户代码文件的父目录是否存在

    • 使用 if (userCodeFile.getParentFile() != null) 判断用户代码文件是否有父目录。如果该文件有父目录,说明它位于某个目录中,需要删除该目录及其所有内容;如果没有父目录,可能是根目录下的文件或其他特殊情况,直接认为删除成功。
  2. 获取用户代码文件所在父目录的绝对路径

    • 使用 userCodeFile.getParentFile().getAbsolutePath() 获取用户代码文件所在父目录的绝对路径。这个路径指向包含 .java 文件及其编译后生成的 .class 文件的目录。
  3. 递归删除整个目录及其内容

    • 调用 FileUtil.del(userCodeParentPath) 方法递归删除指定路径下的整个目录及其所有子目录和文件。FileUtil.del() 是一个工具方法,通常用于简化文件删除操作,并且支持递归删除。
    • 返回值 del 表示删除操作是否成功执行。
  4. 打印删除操作的结果

    • 使用 System.out.println() 打印删除操作的结果,便于调试或记录日志。如果删除成功,输出“删除成功”;如果删除失败,输出“删除失败”。
  5. 返回删除操作的结果

    • 根据删除操作的结果 del,返回相应的布尔值。如果删除成功,返回 true;如果删除失败,返回 false
  6. 处理无父目录的情况

    • 如果用户代码文件没有父目录(即 userCodeFile.getParentFile() 返回 null),则默认认为删除成功,返回 true。这种情况较为罕见,但为了确保方法的健壮性,仍然需要处理。
/**
 * 5. 删除文件
 *
 * 此方法用于删除用户代码文件及其所在的整个目录(包括编译生成的 .class 文件等),
 * 以释放磁盘空间并保持环境清洁。
 *
 * @param userCodeFile 用户代码文件对象(通常为 .java 文件)
 * @return 如果成功删除则返回 true,否则返回 false
 */
public boolean deleteFile(File userCodeFile) {
    // 检查用户代码文件的父目录是否存在
    if (userCodeFile.getParentFile() != null) {
        // 获取用户代码文件所在父目录的绝对路径
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();

        // 使用 FileUtil.del() 方法递归删除整个目录及其内容
        // 返回值表示操作是否成功
        boolean del = FileUtil.del(userCodeParentPath);

        // 打印删除操作的结果(仅用于调试或日志记录)
        System.out.println("删除" + (del ? "成功" : "失败"));

        // 返回删除操作的结果
        return del;
    }

    // 如果用户代码文件没有父目录,则默认认为删除成功(这种情况很少见)
    return true;
}

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

✅ 目的:

对所有可能出现的异常进行捕获和处理,避免沙箱自身崩溃。

📌 原因:
  • 用户代码可能存在语法错误、死循环、异常抛出等问题。
  • 外部命令执行(如 Runtime.exec())可能失败。
  • IO 操作、路径访问、权限控制等都可能引发异常。
  • 需要统一的日志记录、错误码返回机制。

实现步骤:

 

  1. 创建响应对象:初始化一个新的ExecuteCodeResponse对象。
  2. 设置输出列表:将输出列表设置为空列表,表示没有正常输出。
  3. 设置错误消息:从传入的异常对象中获取错误消息,并将其设置到响应对象中。
  4. 设置状态码:将状态码设置为2,表示发生了代码沙箱错误。
  5. 初始化判题信息:初始化一个JudgeInfo对象并设置到响应对象中。
  6. 返回响应对象:返回构建好的ExecuteCodeResponse对象。
/**
 * 获取错误响应的方法
 *
 * @param e 异常对象,包含错误信息
 * @return 包含错误信息的ExecuteCodeResponse对象
 */
private ExecuteCodeResponse getErrorResponse(Throwable e) {
    // 创建一个新的ExecuteCodeResponse对象
    ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
    
    // 设置输出列表为空列表
    executeCodeResponse.setOutputList(new ArrayList<>());
    
    // 设置错误消息为异常对象的消息
    executeCodeResponse.setMessage(e.getMessage());
    
    // 设置状态码为2,表示代码沙箱错误
    executeCodeResponse.setStatus(2);
    
    // 初始化并设置JudgeInfo对象
    executeCodeResponse.setJudgeInfo(new JudgeInfo());
    
    // 返回构建好的ExecuteCodeResponse对象
    return executeCodeResponse;
}



至此,第一期的内容结束,不过我们可以发现一个问题,如果想要上线的话,安全么? 用户提交恶意代码,怎么办?

那针对这种情况,我们可以来提高程序安全性

这部分我放到下一期来讲!


网站公告

今日签到

点亮在社区的每一天
去签到