瀏覽代碼

init code sandbox

rongrunxiang 2 年之前
父節點
當前提交
c159e24162
共有 40 個文件被更改,包括 1758 次插入2 次删除
  1. 34 0
      .gitignore
  2. 0 2
      README.md
  3. 77 0
      pom.xml
  4. 19 0
      src/main/java/com/yupi/yuojcodesandbox/CodeSandbox.java
  5. 200 0
      src/main/java/com/yupi/yuojcodesandbox/JavaCodeSandboxTemplate.java
  6. 205 0
      src/main/java/com/yupi/yuojcodesandbox/JavaDockerCodeSandbox.java
  7. 292 0
      src/main/java/com/yupi/yuojcodesandbox/JavaDockerCodeSandboxOld.java
  8. 17 0
      src/main/java/com/yupi/yuojcodesandbox/JavaNativeCodeSandbox.java
  9. 177 0
      src/main/java/com/yupi/yuojcodesandbox/JavaNativeCodeSandboxOld.java
  10. 10 0
      src/main/java/com/yupi/yuojcodesandbox/SimpleCompute.java
  11. 13 0
      src/main/java/com/yupi/yuojcodesandbox/YuojCodeSandboxApplication.java
  12. 51 0
      src/main/java/com/yupi/yuojcodesandbox/controller/MainController.java
  13. 81 0
      src/main/java/com/yupi/yuojcodesandbox/docker/DockerDemo.java
  14. 21 0
      src/main/java/com/yupi/yuojcodesandbox/model/ExecuteCodeRequest.java
  15. 32 0
      src/main/java/com/yupi/yuojcodesandbox/model/ExecuteCodeResponse.java
  16. 20 0
      src/main/java/com/yupi/yuojcodesandbox/model/ExecuteMessage.java
  17. 25 0
      src/main/java/com/yupi/yuojcodesandbox/model/JudgeInfo.java
  18. 17 0
      src/main/java/com/yupi/yuojcodesandbox/security/DefaultSecurityManager.java
  19. 15 0
      src/main/java/com/yupi/yuojcodesandbox/security/DenySecurityManager.java
  20. 48 0
      src/main/java/com/yupi/yuojcodesandbox/security/MySecurityManager.java
  21. 16 0
      src/main/java/com/yupi/yuojcodesandbox/security/TestSecurityManager.java
  22. 17 0
      src/main/java/com/yupi/yuojcodesandbox/unsafe/MemoryError.java
  23. 20 0
      src/main/java/com/yupi/yuojcodesandbox/unsafe/ReadFileError.java
  24. 27 0
      src/main/java/com/yupi/yuojcodesandbox/unsafe/RunFileError.java
  25. 13 0
      src/main/java/com/yupi/yuojcodesandbox/unsafe/SleepError.java
  26. 22 0
      src/main/java/com/yupi/yuojcodesandbox/unsafe/WriteFileError.java
  27. 117 0
      src/main/java/com/yupi/yuojcodesandbox/utils/ProcessUtils.java
  28. 5 0
      src/main/resources/application.yml
  29. 二進制
      src/main/resources/security/MySecurityManager.class
  30. 46 0
      src/main/resources/security/MySecurityManager.java
  31. 12 0
      src/main/resources/testCode/simpleCompute/Main.java
  32. 二進制
      src/main/resources/testCode/simpleComputeArgs/Main.class
  33. 7 0
      src/main/resources/testCode/simpleComputeArgs/Main.java
  34. 15 0
      src/main/resources/testCode/unsafeCode/MemoryError.java
  35. 18 0
      src/main/resources/testCode/unsafeCode/ReadFileError.java
  36. 25 0
      src/main/resources/testCode/unsafeCode/RunFileError.java
  37. 11 0
      src/main/resources/testCode/unsafeCode/SleepError.java
  38. 19 0
      src/main/resources/testCode/unsafeCode/WriteFileError.java
  39. 1 0
      src/main/resources/木马程序.bat
  40. 13 0
      src/test/java/com/yupi/yuojcodesandbox/YuojCodeSandboxApplicationTests.java

+ 34 - 0
.gitignore

@@ -0,0 +1,34 @@
+tmpCode
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# OnlineJudgeCodeSandbox
-

+ 77 - 0
pom.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.14</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.yupi</groupId>
+    <artifactId>yuoj-code-sandbox</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>yuoj-code-sandbox</name>
+    <description>yuoj-code-sandbox</description>
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.github.docker-java/docker-java -->
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.github.docker-java/docker-java-transport-httpclient5 -->
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-transport-httpclient5</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <!-- https://hutool.cn/docs/index.html#/-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 19 - 0
src/main/java/com/yupi/yuojcodesandbox/CodeSandbox.java

@@ -0,0 +1,19 @@
+package com.yupi.yuojcodesandbox;
+
+
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+
+/**
+ * 代码沙箱接口定义
+ */
+public interface CodeSandbox {
+
+    /**
+     * 执行代码
+     *
+     * @param executeCodeRequest
+     * @return
+     */
+    ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest);
+}

+ 200 - 0
src/main/java/com/yupi/yuojcodesandbox/JavaCodeSandboxTemplate.java

@@ -0,0 +1,200 @@
+package com.yupi.yuojcodesandbox;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import com.yupi.yuojcodesandbox.model.ExecuteMessage;
+import com.yupi.yuojcodesandbox.model.JudgeInfo;
+import com.yupi.yuojcodesandbox.utils.ProcessUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Java 代码沙箱模板方法的实现
+ */
+@Slf4j
+public abstract class JavaCodeSandboxTemplate implements CodeSandbox {
+
+    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
+
+    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";
+
+    private static final long TIME_OUT = 5000L;
+
+    @Override
+    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
+        List<String> inputList = executeCodeRequest.getInputList();
+        String code = executeCodeRequest.getCode();
+        String language = executeCodeRequest.getLanguage();
+
+//        1. 把用户的代码保存为文件
+        File userCodeFile = saveCodeToFile(code);
+
+//        2. 编译代码,得到 class 文件
+        ExecuteMessage compileFileExecuteMessage = compileFile(userCodeFile);
+        System.out.println(compileFileExecuteMessage);
+
+        // 3. 执行代码,得到输出结果
+        List<ExecuteMessage> executeMessageList = runFile(userCodeFile, inputList);
+
+//        4. 收集整理输出结果
+        ExecuteCodeResponse outputResponse = getOutputResponse(executeMessageList);
+
+//        5. 文件清理
+        boolean b = deleteFile(userCodeFile);
+        if (!b) {
+            log.error("deleteFile error, userCodeFilePath = {}", userCodeFile.getAbsolutePath());
+        }
+        return outputResponse;
+    }
+
+
+    /**
+     * 1. 把用户的代码保存为文件
+     * @param code 用户代码
+     * @return
+     */
+    public File saveCodeToFile(String code) {
+        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 userCodeFile;
+    }
+
+    /**
+     * 2、编译代码
+     * @param userCodeFile
+     * @return
+     */
+    public ExecuteMessage compileFile(File userCodeFile) {
+        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
+        try {
+            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
+            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
+            if (executeMessage.getExitValue() != 0) {
+                throw new RuntimeException("编译错误");
+            }
+            return executeMessage;
+        } catch (Exception e) {
+//            return getErrorResponse(e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 3、执行文件,获得执行结果列表
+     * @param userCodeFile
+     * @param inputList
+     * @return
+     */
+    public List<ExecuteMessage> runFile(File userCodeFile, List<String> inputList) {
+        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
+
+        List<ExecuteMessage> executeMessageList = new ArrayList<>();
+        for (String inputArgs : inputList) {
+//            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParentPath, inputArgs);
+            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParentPath, inputArgs);
+            try {
+                Process runProcess = Runtime.getRuntime().exec(runCmd);
+                // 超时控制
+                new Thread(() -> {
+                    try {
+                        Thread.sleep(TIME_OUT);
+                        System.out.println("超时了,中断");
+                        runProcess.destroy();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }).start();
+                ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(runProcess, "运行");
+                System.out.println(executeMessage);
+                executeMessageList.add(executeMessage);
+            } catch (Exception e) {
+                throw new RuntimeException("执行错误", e);
+            }
+        }
+        return executeMessageList;
+    }
+
+    /**
+     * 4、获取输出结果
+     * @param executeMessageList
+     * @return
+     */
+    public ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> executeMessageList) {
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        List<String> outputList = new ArrayList<>();
+        // 取用时最大值,便于判断是否超时
+        long maxTime = 0;
+        for (ExecuteMessage executeMessage : executeMessageList) {
+            String errorMessage = executeMessage.getErrorMessage();
+            if (StrUtil.isNotBlank(errorMessage)) {
+                executeCodeResponse.setMessage(errorMessage);
+                // 用户提交的代码执行中存在错误
+                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()) {
+            executeCodeResponse.setStatus(1);
+        }
+        executeCodeResponse.setOutputList(outputList);
+        JudgeInfo judgeInfo = new JudgeInfo();
+        judgeInfo.setTime(maxTime);
+        // 要借助第三方库来获取内存占用,非常麻烦,此处不做实现
+//        judgeInfo.setMemory();
+        executeCodeResponse.setJudgeInfo(judgeInfo);
+        return executeCodeResponse;
+    }
+
+    /**
+     * 5、删除文件
+     * @param userCodeFile
+     * @return
+     */
+    public boolean deleteFile(File userCodeFile) {
+        if (userCodeFile.getParentFile() != null) {
+            String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
+            boolean del = FileUtil.del(userCodeParentPath);
+            System.out.println("删除" + (del ? "成功" : "失败"));
+            return del;
+        }
+        return true;
+    }
+
+    /**
+     * 6、获取错误响应
+     *
+     * @param e
+     * @return
+     */
+    private ExecuteCodeResponse getErrorResponse(Throwable e) {
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        executeCodeResponse.setOutputList(new ArrayList<>());
+        executeCodeResponse.setMessage(e.getMessage());
+        // 表示代码沙箱错误
+        executeCodeResponse.setStatus(2);
+        executeCodeResponse.setJudgeInfo(new JudgeInfo());
+        return executeCodeResponse;
+    }
+}

+ 205 - 0
src/main/java/com/yupi/yuojcodesandbox/JavaDockerCodeSandbox.java

@@ -0,0 +1,205 @@
+package com.yupi.yuojcodesandbox;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.ArrayUtil;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import com.yupi.yuojcodesandbox.model.ExecuteMessage;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StopWatch;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class JavaDockerCodeSandbox extends JavaCodeSandboxTemplate {
+
+    private static final long TIME_OUT = 5000L;
+
+    private static final Boolean FIRST_INIT = true;
+
+    public static void main(String[] args) {
+        JavaDockerCodeSandbox javaNativeCodeSandbox = new JavaDockerCodeSandbox();
+        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
+        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 3"));
+        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
+//        String code = ResourceUtil.readStr("testCode/unsafeCode/RunFileError.java", StandardCharsets.UTF_8);
+//        String code = ResourceUtil.readStr("testCode/simpleCompute/Main.java", StandardCharsets.UTF_8);
+        executeCodeRequest.setCode(code);
+        executeCodeRequest.setLanguage("java");
+        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
+        System.out.println(executeCodeResponse);
+    }
+
+    /**
+     * 3、创建容器,把文件复制到容器内
+     * @param userCodeFile
+     * @param inputList
+     * @return
+     */
+    @Override
+    public List<ExecuteMessage> runFile(File userCodeFile, List<String> inputList) {
+        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
+        // 获取默认的 Docker Client
+        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
+
+        // 拉取镜像
+        String image = "openjdk:8-alpine";
+        if (FIRST_INIT) {
+            PullImageCmd pullImageCmd = dockerClient.pullImageCmd(image);
+            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
+                @Override
+                public void onNext(PullResponseItem item) {
+                    System.out.println("下载镜像:" + item.getStatus());
+                    super.onNext(item);
+                }
+            };
+            try {
+                pullImageCmd
+                        .exec(pullImageResultCallback)
+                        .awaitCompletion();
+            } catch (InterruptedException e) {
+                System.out.println("拉取镜像异常");
+                throw new RuntimeException(e);
+            }
+        }
+
+        System.out.println("下载完成");
+
+        // 创建容器
+
+        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.withMemory(100 * 1000 * 1000L);
+        hostConfig.withMemorySwap(0L);
+        hostConfig.withCpuCount(1L);
+        hostConfig.withSecurityOpts(Arrays.asList("seccomp=安全管理配置字符串"));
+        hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));
+        CreateContainerResponse createContainerResponse = containerCmd
+                .withHostConfig(hostConfig)
+                .withNetworkDisabled(true)
+                .withReadonlyRootfs(true)
+                .withAttachStdin(true)
+                .withAttachStderr(true)
+                .withAttachStdout(true)
+                .withTty(true)
+                .exec();
+        System.out.println(createContainerResponse);
+        String containerId = createContainerResponse.getId();
+
+        // 启动容器
+        dockerClient.startContainerCmd(containerId).exec();
+
+        // docker exec keen_blackwell java -cp /app Main 1 3
+        // 执行命令并获取结果
+        List<ExecuteMessage> executeMessageList = new ArrayList<>();
+        for (String inputArgs : inputList) {
+            StopWatch stopWatch = new StopWatch();
+            String[] inputArgsArray = inputArgs.split(" ");
+            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputArgsArray);
+            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
+                    .withCmd(cmdArray)
+                    .withAttachStderr(true)
+                    .withAttachStdin(true)
+                    .withAttachStdout(true)
+                    .exec();
+            System.out.println("创建执行命令:" + execCreateCmdResponse);
+
+            ExecuteMessage executeMessage = new ExecuteMessage();
+            final String[] message = {null};
+            final String[] errorMessage = {null};
+            long time = 0L;
+            // 判断是否超时
+            final boolean[] timeout = {true};
+            String execId = execCreateCmdResponse.getId();
+            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
+                @Override
+                public void onComplete() {
+                    // 如果执行完成,则表示没超时
+                    timeout[0] = false;
+                    super.onComplete();
+                }
+
+                @Override
+                public void onNext(Frame frame) {
+                    StreamType streamType = frame.getStreamType();
+                    if (StreamType.STDERR.equals(streamType)) {
+                        errorMessage[0] = new String(frame.getPayload());
+                        System.out.println("输出错误结果:" + errorMessage[0]);
+                    } else {
+                        message[0] = new String(frame.getPayload());
+                        System.out.println("输出结果:" + message[0]);
+                    }
+                    super.onNext(frame);
+                }
+            };
+
+            final long[] maxMemory = {0L};
+
+            // 获取占用的内存
+            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
+            ResultCallback<Statistics> statisticsResultCallback = statsCmd.exec(new ResultCallback<Statistics>() {
+
+                @Override
+                public void onNext(Statistics statistics) {
+                    System.out.println("内存占用:" + statistics.getMemoryStats().getUsage());
+                    maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
+                }
+
+                @Override
+                public void close() throws IOException {
+
+                }
+
+                @Override
+                public void onStart(Closeable closeable) {
+
+                }
+
+                @Override
+                public void onError(Throwable throwable) {
+
+                }
+
+                @Override
+                public void onComplete() {
+
+                }
+            });
+            statsCmd.exec(statisticsResultCallback);
+            try {
+                stopWatch.start();
+                dockerClient.execStartCmd(execId)
+                        .exec(execStartResultCallback)
+                        .awaitCompletion(TIME_OUT, TimeUnit.MICROSECONDS);
+                stopWatch.stop();
+                time = stopWatch.getLastTaskTimeMillis();
+                statsCmd.close();
+            } catch (InterruptedException e) {
+                System.out.println("程序执行异常");
+                throw new RuntimeException(e);
+            }
+            executeMessage.setMessage(message[0]);
+            executeMessage.setErrorMessage(errorMessage[0]);
+            executeMessage.setTime(time);
+            executeMessage.setMemory(maxMemory[0]);
+            executeMessageList.add(executeMessage);
+        }
+        return executeMessageList;
+    }
+}
+
+
+

+ 292 - 0
src/main/java/com/yupi/yuojcodesandbox/JavaDockerCodeSandboxOld.java

@@ -0,0 +1,292 @@
+package com.yupi.yuojcodesandbox;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import com.yupi.yuojcodesandbox.model.ExecuteMessage;
+import com.yupi.yuojcodesandbox.model.JudgeInfo;
+import com.yupi.yuojcodesandbox.utils.ProcessUtils;
+import org.springframework.util.StopWatch;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+public class JavaDockerCodeSandboxOld implements CodeSandbox {
+
+    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
+
+    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";
+
+    private static final long TIME_OUT = 5000L;
+
+    private static final String SECURITY_MANAGER_PATH = "C:\\code\\yuoj-code-sandbox\\src\\main\\resources\\security";
+
+    private static final String SECURITY_MANAGER_CLASS_NAME = "MySecurityManager";
+
+    private static final Boolean FIRST_INIT = true;
+
+    public static void main(String[] args) {
+        JavaDockerCodeSandboxOld javaNativeCodeSandbox = new JavaDockerCodeSandboxOld();
+        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
+        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 3"));
+        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
+//        String code = ResourceUtil.readStr("testCode/unsafeCode/RunFileError.java", StandardCharsets.UTF_8);
+//        String code = ResourceUtil.readStr("testCode/simpleCompute/Main.java", StandardCharsets.UTF_8);
+        executeCodeRequest.setCode(code);
+        executeCodeRequest.setLanguage("java");
+        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
+        System.out.println(executeCodeResponse);
+    }
+
+    @Override
+    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
+//        System.setSecurityManager(new DenySecurityManager());
+
+        List<String> inputList = executeCodeRequest.getInputList();
+        String code = executeCodeRequest.getCode();
+        String language = executeCodeRequest.getLanguage();
+
+//        1. 把用户的代码保存为文件
+
+        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);
+
+//        2. 编译代码,得到 class 文件
+        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
+        try {
+            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
+            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
+            System.out.println(executeMessage);
+        } catch (Exception e) {
+            return getErrorResponse(e);
+        }
+
+        // 3. 创建容器,把文件复制到容器内
+        // 获取默认的 Docker Client
+        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
+
+        // 拉取镜像
+        String image = "openjdk:8-alpine";
+        if (FIRST_INIT) {
+            PullImageCmd pullImageCmd = dockerClient.pullImageCmd(image);
+            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
+                @Override
+                public void onNext(PullResponseItem item) {
+                    System.out.println("下载镜像:" + item.getStatus());
+                    super.onNext(item);
+                }
+            };
+            try {
+                pullImageCmd
+                        .exec(pullImageResultCallback)
+                        .awaitCompletion();
+            } catch (InterruptedException e) {
+                System.out.println("拉取镜像异常");
+                throw new RuntimeException(e);
+            }
+        }
+
+        System.out.println("下载完成");
+
+        // 创建容器
+
+        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.withMemory(100 * 1000 * 1000L);
+        hostConfig.withMemorySwap(0L);
+        hostConfig.withCpuCount(1L);
+        hostConfig.withSecurityOpts(Arrays.asList("seccomp=安全管理配置字符串"));
+        hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));
+        CreateContainerResponse createContainerResponse = containerCmd
+                .withHostConfig(hostConfig)
+                .withNetworkDisabled(true)
+                .withReadonlyRootfs(true)
+                .withAttachStdin(true)
+                .withAttachStderr(true)
+                .withAttachStdout(true)
+                .withTty(true)
+                .exec();
+        System.out.println(createContainerResponse);
+        String containerId = createContainerResponse.getId();
+
+        // 启动容器
+        dockerClient.startContainerCmd(containerId).exec();
+
+        // docker exec keen_blackwell java -cp /app Main 1 3
+        // 执行命令并获取结果
+        List<ExecuteMessage> executeMessageList = new ArrayList<>();
+        for (String inputArgs : inputList) {
+            StopWatch stopWatch = new StopWatch();
+            String[] inputArgsArray = inputArgs.split(" ");
+            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputArgsArray);
+            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
+                    .withCmd(cmdArray)
+                    .withAttachStderr(true)
+                    .withAttachStdin(true)
+                    .withAttachStdout(true)
+                    .exec();
+            System.out.println("创建执行命令:" + execCreateCmdResponse);
+
+            ExecuteMessage executeMessage = new ExecuteMessage();
+            final String[] message = {null};
+            final String[] errorMessage = {null};
+            long time = 0L;
+            // 判断是否超时
+            final boolean[] timeout = {true};
+            String execId = execCreateCmdResponse.getId();
+            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
+                @Override
+                public void onComplete() {
+                    // 如果执行完成,则表示没超时
+                    timeout[0] = false;
+                    super.onComplete();
+                }
+
+                @Override
+                public void onNext(Frame frame) {
+                    StreamType streamType = frame.getStreamType();
+                    if (StreamType.STDERR.equals(streamType)) {
+                        errorMessage[0] = new String(frame.getPayload());
+                        System.out.println("输出错误结果:" + errorMessage[0]);
+                    } else {
+                        message[0] = new String(frame.getPayload());
+                        System.out.println("输出结果:" + message[0]);
+                    }
+                    super.onNext(frame);
+                }
+            };
+
+            final long[] maxMemory = {0L};
+
+            // 获取占用的内存
+            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
+            ResultCallback<Statistics> statisticsResultCallback = statsCmd.exec(new ResultCallback<Statistics>() {
+
+                @Override
+                public void onNext(Statistics statistics) {
+                    System.out.println("内存占用:" + statistics.getMemoryStats().getUsage());
+                    maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
+                }
+
+                @Override
+                public void close() throws IOException {
+
+                }
+
+                @Override
+                public void onStart(Closeable closeable) {
+
+                }
+
+                @Override
+                public void onError(Throwable throwable) {
+
+                }
+
+                @Override
+                public void onComplete() {
+
+                }
+            });
+            statsCmd.exec(statisticsResultCallback);
+            try {
+                stopWatch.start();
+                dockerClient.execStartCmd(execId)
+                        .exec(execStartResultCallback)
+                        .awaitCompletion(TIME_OUT, TimeUnit.MICROSECONDS);
+                stopWatch.stop();
+                time = stopWatch.getLastTaskTimeMillis();
+                statsCmd.close();
+            } catch (InterruptedException e) {
+                System.out.println("程序执行异常");
+                throw new RuntimeException(e);
+            }
+            executeMessage.setMessage(message[0]);
+            executeMessage.setErrorMessage(errorMessage[0]);
+            executeMessage.setTime(time);
+            executeMessage.setMemory(maxMemory[0]);
+            executeMessageList.add(executeMessage);
+        }
+        // 4、封装结果,跟原生实现方式完全一致
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        List<String> outputList = new ArrayList<>();
+        // 取用时最大值,便于判断是否超时
+        long maxTime = 0;
+        for (ExecuteMessage executeMessage : executeMessageList) {
+            String errorMessage = executeMessage.getErrorMessage();
+            if (StrUtil.isNotBlank(errorMessage)) {
+                executeCodeResponse.setMessage(errorMessage);
+                // 用户提交的代码执行中存在错误
+                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()) {
+            executeCodeResponse.setStatus(1);
+        }
+        executeCodeResponse.setOutputList(outputList);
+        JudgeInfo judgeInfo = new JudgeInfo();
+        judgeInfo.setTime(maxTime);
+        // 要借助第三方库来获取内存占用,非常麻烦,此处不做实现
+//        judgeInfo.setMemory();
+
+        executeCodeResponse.setJudgeInfo(judgeInfo);
+
+//        5. 文件清理
+        if (userCodeFile.getParentFile() != null) {
+            boolean del = FileUtil.del(userCodeParentPath);
+            System.out.println("删除" + (del ? "成功" : "失败"));
+        }
+        return executeCodeResponse;
+    }
+
+    /**
+     * 获取错误响应
+     *
+     * @param e
+     * @return
+     */
+    private ExecuteCodeResponse getErrorResponse(Throwable e) {
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        executeCodeResponse.setOutputList(new ArrayList<>());
+        executeCodeResponse.setMessage(e.getMessage());
+        // 表示代码沙箱错误
+        executeCodeResponse.setStatus(2);
+        executeCodeResponse.setJudgeInfo(new JudgeInfo());
+        return executeCodeResponse;
+    }
+}
+
+
+

+ 17 - 0
src/main/java/com/yupi/yuojcodesandbox/JavaNativeCodeSandbox.java

@@ -0,0 +1,17 @@
+package com.yupi.yuojcodesandbox;
+
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import org.springframework.stereotype.Component;
+
+/**
+ * Java 原生代码沙箱实现(直接复用模板方法)
+ */
+@Component
+public class JavaNativeCodeSandbox extends JavaCodeSandboxTemplate {
+
+    @Override
+    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
+        return super.executeCode(executeCodeRequest);
+    }
+}

+ 177 - 0
src/main/java/com/yupi/yuojcodesandbox/JavaNativeCodeSandboxOld.java

@@ -0,0 +1,177 @@
+package com.yupi.yuojcodesandbox;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.dfa.WordTree;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import com.yupi.yuojcodesandbox.model.ExecuteMessage;
+import com.yupi.yuojcodesandbox.model.JudgeInfo;
+import com.yupi.yuojcodesandbox.utils.ProcessUtils;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+public class JavaNativeCodeSandboxOld implements CodeSandbox {
+
+    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
+
+    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";
+
+    private static final long TIME_OUT = 5000L;
+
+    private static final String SECURITY_MANAGER_PATH = "C:\\code\\yuoj-code-sandbox\\src\\main\\resources\\security";
+
+    private static final String SECURITY_MANAGER_CLASS_NAME = "MySecurityManager";
+
+    private static final List<String> blackList = Arrays.asList("Files", "exec");
+
+    private static final WordTree WORD_TREE;
+
+    static {
+        // 初始化字典树
+        WORD_TREE = new WordTree();
+        WORD_TREE.addWords(blackList);
+    }
+
+    public static void main(String[] args) {
+        JavaNativeCodeSandboxOld javaNativeCodeSandbox = new JavaNativeCodeSandboxOld();
+        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
+        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 3"));
+//        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
+        String code = ResourceUtil.readStr("testCode/unsafeCode/RunFileError.java", StandardCharsets.UTF_8);
+//        String code = ResourceUtil.readStr("testCode/simpleCompute/Main.java", StandardCharsets.UTF_8);
+        executeCodeRequest.setCode(code);
+        executeCodeRequest.setLanguage("java");
+        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
+        System.out.println(executeCodeResponse);
+    }
+
+    @Override
+    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
+//        System.setSecurityManager(new DenySecurityManager());
+
+        List<String> inputList = executeCodeRequest.getInputList();
+        String code = executeCodeRequest.getCode();
+        String language = executeCodeRequest.getLanguage();
+
+       //  校验代码中是否包含黑名单中的命令
+//        FoundWord foundWord = WORD_TREE.matchWord(code);
+//        if (foundWord != null) {
+//            System.out.println("包含禁止词:" + foundWord.getFoundWord());
+//            return null;
+//        }
+
+//        1. 把用户的代码保存为文件
+
+        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);
+
+//        2. 编译代码,得到 class 文件
+        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
+        try {
+            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
+            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
+            System.out.println(executeMessage);
+        } catch (Exception e) {
+            return getErrorResponse(e);
+        }
+
+        // 3. 执行代码,得到输出结果
+        List<ExecuteMessage> executeMessageList = new ArrayList<>();
+        for (String inputArgs : inputList) {
+//            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParentPath, inputArgs);
+            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=%s Main %s", userCodeParentPath, SECURITY_MANAGER_PATH, SECURITY_MANAGER_CLASS_NAME, inputArgs);
+            try {
+                Process runProcess = Runtime.getRuntime().exec(runCmd);
+                // 超时控制
+                new Thread(() -> {
+                    try {
+                        Thread.sleep(TIME_OUT);
+                        System.out.println("超时了,中断");
+                        runProcess.destroy();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }).start();
+                ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(runProcess, "运行");
+//                ExecuteMessage executeMessage = ProcessUtils.runInteractProcessAndGetMessage(runProcess, inputArgs);
+                System.out.println(executeMessage);
+                executeMessageList.add(executeMessage);
+            } catch (Exception e) {
+                return getErrorResponse(e);
+            }
+        }
+
+//        4. 收集整理输出结果
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        List<String> outputList = new ArrayList<>();
+        // 取用时最大值,便于判断是否超时
+        long maxTime = 0;
+        for (ExecuteMessage executeMessage : executeMessageList) {
+            String errorMessage = executeMessage.getErrorMessage();
+            if (StrUtil.isNotBlank(errorMessage)) {
+                executeCodeResponse.setMessage(errorMessage);
+                // 用户提交的代码执行中存在错误
+                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()) {
+            executeCodeResponse.setStatus(1);
+        }
+        executeCodeResponse.setOutputList(outputList);
+        JudgeInfo judgeInfo = new JudgeInfo();
+        judgeInfo.setTime(maxTime);
+        // 要借助第三方库来获取内存占用,非常麻烦,此处不做实现
+//        judgeInfo.setMemory();
+
+        executeCodeResponse.setJudgeInfo(judgeInfo);
+
+//        5. 文件清理
+        if (userCodeFile.getParentFile() != null) {
+            boolean del = FileUtil.del(userCodeParentPath);
+            System.out.println("删除" + (del ? "成功" : "失败"));
+        }
+        return executeCodeResponse;
+    }
+
+    /**
+     * 获取错误响应
+     *
+     * @param e
+     * @return
+     */
+    private ExecuteCodeResponse getErrorResponse(Throwable e) {
+        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
+        executeCodeResponse.setOutputList(new ArrayList<>());
+        executeCodeResponse.setMessage(e.getMessage());
+        // 表示代码沙箱错误
+        executeCodeResponse.setStatus(2);
+        executeCodeResponse.setJudgeInfo(new JudgeInfo());
+        return executeCodeResponse;
+    }
+}
+
+
+

+ 10 - 0
src/main/java/com/yupi/yuojcodesandbox/SimpleCompute.java

@@ -0,0 +1,10 @@
+package com.yupi.yuojcodesandbox;
+
+public class SimpleCompute {
+
+    public static void main(String[] args) {
+        int a = Integer.parseInt(args[0]);
+        int b = Integer.parseInt(args[1]);
+        System.out.println("结果:" + (a + b));
+    }
+}

+ 13 - 0
src/main/java/com/yupi/yuojcodesandbox/YuojCodeSandboxApplication.java

@@ -0,0 +1,13 @@
+package com.yupi.yuojcodesandbox;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class YuojCodeSandboxApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(YuojCodeSandboxApplication.class, args);
+    }
+
+}

+ 51 - 0
src/main/java/com/yupi/yuojcodesandbox/controller/MainController.java

@@ -0,0 +1,51 @@
+package com.yupi.yuojcodesandbox.controller;
+
+import com.yupi.yuojcodesandbox.JavaNativeCodeSandbox;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeRequest;
+import com.yupi.yuojcodesandbox.model.ExecuteCodeResponse;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RestController("/")
+public class MainController {
+
+    // 定义鉴权请求头和密钥
+    private static final String AUTH_REQUEST_HEADER = "auth";
+
+    private static final String AUTH_REQUEST_SECRET = "secretKey";
+
+    @Resource
+    private JavaNativeCodeSandbox javaNativeCodeSandbox;
+
+    @GetMapping("/health")
+    public String healthCheck() {
+        return "ok";
+    }
+
+    /**
+     * 执行代码
+     *
+     * @param executeCodeRequest
+     * @return
+     */
+    @PostMapping("/executeCode")
+    ExecuteCodeResponse executeCode(@RequestBody ExecuteCodeRequest executeCodeRequest, HttpServletRequest request,
+                                    HttpServletResponse response) {
+        // 基本的认证
+        String authHeader = request.getHeader(AUTH_REQUEST_HEADER);
+        if (!AUTH_REQUEST_SECRET.equals(authHeader)) {
+            response.setStatus(403);
+            return null;
+        }
+        if (executeCodeRequest == null) {
+            throw new RuntimeException("请求参数为空");
+        }
+        return javaNativeCodeSandbox.executeCode(executeCodeRequest);
+    }
+}

+ 81 - 0
src/main/java/com/yupi/yuojcodesandbox/docker/DockerDemo.java

@@ -0,0 +1,81 @@
+package com.yupi.yuojcodesandbox.docker;
+
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.model.Container;
+import com.github.dockerjava.api.model.Frame;
+import com.github.dockerjava.api.model.PullResponseItem;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.DockerClientImpl;
+import com.github.dockerjava.core.command.LogContainerResultCallback;
+
+import java.util.List;
+
+public class DockerDemo {
+
+    public static void main(String[] args) throws InterruptedException {
+        // 获取默认的 Docker Client
+        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
+//        PingCmd pingCmd = dockerClient.pingCmd();
+//        pingCmd.exec();
+        // 拉取镜像
+        String image = "nginx:latest";
+//        PullImageCmd pullImageCmd = dockerClient.pullImageCmd(image);
+//        PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
+//            @Override
+//            public void onNext(PullResponseItem item) {
+//                System.out.println("下载镜像:" + item.getStatus());
+//                super.onNext(item);
+//            }
+//        };
+//        pullImageCmd
+//                .exec(pullImageResultCallback)
+//                .awaitCompletion();
+//        System.out.println("下载完成");
+        // 创建容器
+        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);
+        CreateContainerResponse createContainerResponse = containerCmd
+                .withCmd("echo", "Hello Docker")
+                .exec();
+        System.out.println(createContainerResponse);
+        String containerId = createContainerResponse.getId();
+
+        // 查看容器状态
+        ListContainersCmd listContainersCmd = dockerClient.listContainersCmd();
+        List<Container> containerList = listContainersCmd.withShowAll(true).exec();
+        for (Container container : containerList) {
+            System.out.println(container);
+        }
+
+        // 启动容器
+        dockerClient.startContainerCmd(containerId).exec();
+
+//        Thread.sleep(5000L);
+
+        // 查看日志
+
+        LogContainerResultCallback logContainerResultCallback = new LogContainerResultCallback() {
+            @Override
+            public void onNext(Frame item) {
+                System.out.println(item.getStreamType());
+                System.out.println("日志:" + new String(item.getPayload()));
+                super.onNext(item);
+            }
+        };
+
+
+        dockerClient.logContainerCmd(containerId)
+                .withStdErr(true)
+                .withStdOut(true)
+                .exec(logContainerResultCallback)
+                .awaitCompletion();
+
+        // 删除容器
+        dockerClient.removeContainerCmd(containerId).withForce(true).exec();
+
+        // 删除镜像
+        dockerClient.removeImageCmd(image).exec();
+    }
+}

+ 21 - 0
src/main/java/com/yupi/yuojcodesandbox/model/ExecuteCodeRequest.java

@@ -0,0 +1,21 @@
+package com.yupi.yuojcodesandbox.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExecuteCodeRequest {
+
+    private List<String> inputList;
+
+    private String code;
+
+    private String language;
+}

+ 32 - 0
src/main/java/com/yupi/yuojcodesandbox/model/ExecuteCodeResponse.java

@@ -0,0 +1,32 @@
+package com.yupi.yuojcodesandbox.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExecuteCodeResponse {
+
+    private List<String> outputList;
+
+    /**
+     * 接口信息
+     */
+    private String message;
+
+    /**
+     * 执行状态
+     */
+    private Integer status;
+
+    /**
+     * 判题信息
+     */
+    private JudgeInfo judgeInfo;
+}

+ 20 - 0
src/main/java/com/yupi/yuojcodesandbox/model/ExecuteMessage.java

@@ -0,0 +1,20 @@
+package com.yupi.yuojcodesandbox.model;
+
+import lombok.Data;
+
+/**
+ * 进程执行信息
+ */
+@Data
+public class ExecuteMessage {
+
+    private Integer exitValue;
+
+    private String message;
+
+    private String errorMessage;
+
+    private Long time;
+
+    private Long memory;
+}

+ 25 - 0
src/main/java/com/yupi/yuojcodesandbox/model/JudgeInfo.java

@@ -0,0 +1,25 @@
+package com.yupi.yuojcodesandbox.model;
+
+import lombok.Data;
+
+/**
+ * 判题信息
+ */
+@Data
+public class JudgeInfo {
+
+    /**
+     * 程序执行信息
+     */
+    private String message;
+
+    /**
+     * 消耗内存
+     */
+    private Long memory;
+
+    /**
+     * 消耗时间(KB)
+     */
+    private Long time;
+}

+ 17 - 0
src/main/java/com/yupi/yuojcodesandbox/security/DefaultSecurityManager.java

@@ -0,0 +1,17 @@
+package com.yupi.yuojcodesandbox.security;
+
+import java.security.Permission;
+
+/**
+ * 默认安全管理器
+ */
+public class DefaultSecurityManager extends SecurityManager {
+
+    // 检查所有的权限
+    @Override
+    public void checkPermission(Permission perm) {
+        System.out.println("默认不做任何限制");
+        System.out.println(perm);
+//        super.checkPermission(perm);
+    }
+}

+ 15 - 0
src/main/java/com/yupi/yuojcodesandbox/security/DenySecurityManager.java

@@ -0,0 +1,15 @@
+package com.yupi.yuojcodesandbox.security;
+
+import java.security.Permission;
+
+/**
+ * 禁用所有权限安全管理器
+ */
+public class DenySecurityManager extends SecurityManager {
+
+    // 检查所有的权限
+    @Override
+    public void checkPermission(Permission perm) {
+        throw new SecurityException("权限异常:" + perm.toString());
+    }
+}

+ 48 - 0
src/main/java/com/yupi/yuojcodesandbox/security/MySecurityManager.java

@@ -0,0 +1,48 @@
+package com.yupi.yuojcodesandbox.security;
+
+import java.security.Permission;
+
+public class MySecurityManager extends SecurityManager {
+
+
+    // 检查所有的权限
+    @Override
+    public void checkPermission(Permission perm) {
+//        super.checkPermission(perm);
+    }
+
+    // 检测程序是否可执行文件
+    @Override
+    public void checkExec(String cmd) {
+        throw new SecurityException("checkExec 权限异常:" + cmd);
+    }
+
+    // 检测程序是否允许读文件
+
+    @Override
+    public void checkRead(String file) {
+        System.out.println(file);
+        if (file.contains("C:\\code\\yuoj-code-sandbox")) {
+            return;
+        }
+//        throw new SecurityException("checkRead 权限异常:" + file);
+    }
+
+    // 检测程序是否允许写文件
+    @Override
+    public void checkWrite(String file) {
+//        throw new SecurityException("checkWrite 权限异常:" + file);
+    }
+
+    // 检测程序是否允许删除文件
+    @Override
+    public void checkDelete(String file) {
+//        throw new SecurityException("checkDelete 权限异常:" + file);
+    }
+
+    // 检测程序是否允许连接网络
+    @Override
+    public void checkConnect(String host, int port) {
+//        throw new SecurityException("checkConnect 权限异常:" + host + ":" + port);
+    }
+}

+ 16 - 0
src/main/java/com/yupi/yuojcodesandbox/security/TestSecurityManager.java

@@ -0,0 +1,16 @@
+package com.yupi.yuojcodesandbox.security;
+
+import cn.hutool.core.io.FileUtil;
+
+import java.nio.charset.Charset;
+
+/**
+ * 测试安全管理器
+ */
+public class TestSecurityManager {
+
+    public static void main(String[] args) {
+        System.setSecurityManager(new MySecurityManager());
+        FileUtil.writeString("aa", "aaa", Charset.defaultCharset());
+    }
+}

+ 17 - 0
src/main/java/com/yupi/yuojcodesandbox/unsafe/MemoryError.java

@@ -0,0 +1,17 @@
+package com.yupi.yuojcodesandbox.unsafe;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 无限占用空间(浪费系统内存)
+ */
+public class MemoryError {
+
+    public static void main(String[] args) throws InterruptedException {
+        List<byte[]> bytes = new ArrayList<>();
+        while (true) {
+            bytes.add(new byte[10000]);
+        }
+    }
+}

+ 20 - 0
src/main/java/com/yupi/yuojcodesandbox/unsafe/ReadFileError.java

@@ -0,0 +1,20 @@
+package com.yupi.yuojcodesandbox.unsafe;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+/**
+ * 读取服务器文件(文件信息泄露)
+ */
+public class ReadFileError {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/application.yml";
+        List<String> allLines = Files.readAllLines(Paths.get(filePath));
+        System.out.println(String.join("\n", allLines));
+    }
+}

+ 27 - 0
src/main/java/com/yupi/yuojcodesandbox/unsafe/RunFileError.java

@@ -0,0 +1,27 @@
+package com.yupi.yuojcodesandbox.unsafe;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 运行其他程序(比如危险木马)
+ */
+public class RunFileError {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/木马程序.bat";
+        Process process = Runtime.getRuntime().exec(filePath);
+        process.waitFor();
+        // 分批获取进程的正常输出
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+        // 逐行读取
+        String compileOutputLine;
+        while ((compileOutputLine = bufferedReader.readLine()) != null) {
+            System.out.println(compileOutputLine);
+        }
+        System.out.println("执行异常程序成功");
+    }
+}

+ 13 - 0
src/main/java/com/yupi/yuojcodesandbox/unsafe/SleepError.java

@@ -0,0 +1,13 @@
+package com.yupi.yuojcodesandbox.unsafe;
+
+/**
+ * 无限睡眠(阻塞程序执行)
+ */
+public class SleepError {
+
+    public static void main(String[] args) throws InterruptedException {
+        long ONE_HOUR = 60 * 60 * 1000L;
+        Thread.sleep(ONE_HOUR);
+        System.out.println("睡完了");
+    }
+}

+ 22 - 0
src/main/java/com/yupi/yuojcodesandbox/unsafe/WriteFileError.java

@@ -0,0 +1,22 @@
+package com.yupi.yuojcodesandbox.unsafe;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 向服务器写文件(植入危险程序)
+ */
+public class WriteFileError {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/木马程序.bat";
+        String errorProgram = "java -version 2>&1";
+        Files.write(Paths.get(filePath), Arrays.asList(errorProgram));
+        System.out.println("写木马成功,你完了哈哈");
+    }
+}

+ 117 - 0
src/main/java/com/yupi/yuojcodesandbox/utils/ProcessUtils.java

@@ -0,0 +1,117 @@
+package com.yupi.yuojcodesandbox.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.yupi.yuojcodesandbox.model.ExecuteMessage;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.StopWatch;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 进程工具类
+ */
+public class ProcessUtils {
+
+    /**
+     * 执行进程并获取信息
+     *
+     * @param runProcess
+     * @param opName
+     * @return
+     */
+    public static ExecuteMessage runProcessAndGetMessage(Process runProcess, String opName) {
+        ExecuteMessage executeMessage = new ExecuteMessage();
+
+        try {
+            StopWatch stopWatch = new StopWatch();
+            stopWatch.start();
+            // 等待程序执行,获取错误码
+            int exitValue = runProcess.waitFor();
+            executeMessage.setExitValue(exitValue);
+            // 正常退出
+            if (exitValue == 0) {
+                System.out.println(opName + "成功");
+                // 分批获取进程的正常输出
+                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()));
+                List<String> outputStrList = new ArrayList<>();
+                // 逐行读取
+                String compileOutputLine;
+                while ((compileOutputLine = bufferedReader.readLine()) != null) {
+                    outputStrList.add(compileOutputLine);
+                }
+                executeMessage.setMessage(StringUtils.join(outputStrList, "\n"));
+            } else {
+                // 异常退出
+                System.out.println(opName + "失败,错误码: " + exitValue);
+                // 分批获取进程的正常输出
+                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()));
+                List<String> outputStrList = new ArrayList<>();
+                // 逐行读取
+                String compileOutputLine;
+                while ((compileOutputLine = bufferedReader.readLine()) != null) {
+                    outputStrList.add(compileOutputLine);
+                }
+                executeMessage.setMessage(StringUtils.join(outputStrList, "\n"));
+
+                // 分批获取进程的错误输出
+                BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(runProcess.getErrorStream()));
+                // 逐行读取
+                List<String> errorOutputStrList = new ArrayList<>();
+                // 逐行读取
+                String errorCompileOutputLine;
+                while ((errorCompileOutputLine = errorBufferedReader.readLine()) != null) {
+                    errorOutputStrList.add(errorCompileOutputLine);
+                }
+                executeMessage.setErrorMessage(StringUtils.join(errorOutputStrList, "\n"));
+            }
+            stopWatch.stop();
+            executeMessage.setTime(stopWatch.getLastTaskTimeMillis());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return executeMessage;
+    }
+
+    /**
+     * 执行交互式进程并获取信息
+     *
+     * @param runProcess
+     * @param args
+     * @return
+     */
+    public static ExecuteMessage runInteractProcessAndGetMessage(Process runProcess, String args) {
+        ExecuteMessage executeMessage = new ExecuteMessage();
+
+        try {
+            // 向控制台输入程序
+            OutputStream outputStream = runProcess.getOutputStream();
+            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
+            String[] s = args.split(" ");
+            String join = StrUtil.join("\n", s) + "\n";
+            outputStreamWriter.write(join);
+            // 相当于按了回车,执行输入的发送
+            outputStreamWriter.flush();
+
+            // 分批获取进程的正常输出
+            InputStream inputStream = runProcess.getInputStream();
+            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+            StringBuilder compileOutputStringBuilder = new StringBuilder();
+            // 逐行读取
+            String compileOutputLine;
+            while ((compileOutputLine = bufferedReader.readLine()) != null) {
+                compileOutputStringBuilder.append(compileOutputLine);
+            }
+            executeMessage.setMessage(compileOutputStringBuilder.toString());
+            // 记得资源的释放,否则会卡死
+            outputStreamWriter.close();
+            outputStream.close();
+            inputStream.close();
+            runProcess.destroy();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return executeMessage;
+    }
+}

+ 5 - 0
src/main/resources/application.yml

@@ -0,0 +1,5 @@
+server:
+  port: 8090
+#spring:
+#  datasource:
+#    password: 123456

二進制
src/main/resources/security/MySecurityManager.class


+ 46 - 0
src/main/resources/security/MySecurityManager.java

@@ -0,0 +1,46 @@
+import java.security.Permission;
+
+public class MySecurityManager extends SecurityManager {
+
+
+    // 检查所有的权限
+    @Override
+    public void checkPermission(Permission perm) {
+//        super.checkPermission(perm);
+    }
+
+    // 检测程序是否可执行文件
+    @Override
+    public void checkExec(String cmd) {
+        throw new SecurityException("checkExec 权限异常:" + cmd);
+    }
+
+    // 检测程序是否允许读文件
+
+    @Override
+    public void checkRead(String file) {
+        System.out.println(file);
+        if (file.contains("C:\\code\\yuoj-code-sandbox")) {
+            return;
+        }
+//        throw new SecurityException("checkRead 权限异常:" + file);
+    }
+
+    // 检测程序是否允许写文件
+    @Override
+    public void checkWrite(String file) {
+//        throw new SecurityException("checkWrite 权限异常:" + file);
+    }
+
+    // 检测程序是否允许删除文件
+    @Override
+    public void checkDelete(String file) {
+//        throw new SecurityException("checkDelete 权限异常:" + file);
+    }
+
+    // 检测程序是否允许连接网络
+    @Override
+    public void checkConnect(String host, int port) {
+//        throw new SecurityException("checkConnect 权限异常:" + host + ":" + port);
+    }
+}

+ 12 - 0
src/main/resources/testCode/simpleCompute/Main.java

@@ -0,0 +1,12 @@
+import java.io.*;
+import java.util.*;
+
+public class Main
+{
+    public static void main(String args[]) throws Exception
+    {
+        Scanner cin=new Scanner(System.in);
+        int a=cin.nextInt(),b=cin.nextInt();
+        System.out.println(a+b);
+    }
+}

二進制
src/main/resources/testCode/simpleComputeArgs/Main.class


+ 7 - 0
src/main/resources/testCode/simpleComputeArgs/Main.java

@@ -0,0 +1,7 @@
+public class Main {
+    public static void main(String[] args) {
+        int a = Integer.parseInt(args[0]);
+        int b = Integer.parseInt(args[1]);
+        System.out.println("结果:" + (a + b));
+    }
+}

+ 15 - 0
src/main/resources/testCode/unsafeCode/MemoryError.java

@@ -0,0 +1,15 @@
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 无限占用空间(浪费系统内存)
+ */
+public class Main {
+
+    public static void main(String[] args) throws InterruptedException {
+        List<byte[]> bytes = new ArrayList<>();
+        while (true) {
+            bytes.add(new byte[10000]);
+        }
+    }
+}

+ 18 - 0
src/main/resources/testCode/unsafeCode/ReadFileError.java

@@ -0,0 +1,18 @@
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+/**
+ * 读取服务器文件(文件信息泄露)
+ */
+public class Main {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/application.yml";
+        List<String> allLines = Files.readAllLines(Paths.get(filePath));
+        System.out.println(String.join("\n", allLines));
+    }
+}

+ 25 - 0
src/main/resources/testCode/unsafeCode/RunFileError.java

@@ -0,0 +1,25 @@
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 运行其他程序(比如危险木马)
+ */
+public class Main {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/木马程序.bat";
+        Process process = Runtime.getRuntime().exec(filePath);
+        process.waitFor();
+        // 分批获取进程的正常输出
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+        // 逐行读取
+        String compileOutputLine;
+        while ((compileOutputLine = bufferedReader.readLine()) != null) {
+            System.out.println(compileOutputLine);
+        }
+        System.out.println("执行异常程序成功");
+    }
+}

+ 11 - 0
src/main/resources/testCode/unsafeCode/SleepError.java

@@ -0,0 +1,11 @@
+/**
+ * 无限睡眠(阻塞程序执行)
+ */
+public class Main {
+
+    public static void main(String[] args) throws InterruptedException {
+        long ONE_HOUR = 60 * 60 * 1000L;
+        Thread.sleep(ONE_HOUR);
+        System.out.println("睡完了");
+    }
+}

+ 19 - 0
src/main/resources/testCode/unsafeCode/WriteFileError.java

@@ -0,0 +1,19 @@
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+/**
+ * 向服务器写文件(植入危险程序)
+ */
+public class Main {
+
+    public static void main(String[] args) throws InterruptedException, IOException {
+        String userDir = System.getProperty("user.dir");
+        String filePath = userDir + File.separator + "src/main/resources/木马程序.bat";
+        String errorProgram = "java -version 2>&1";
+        Files.write(Paths.get(filePath), Arrays.asList(errorProgram));
+        System.out.println("写木马成功,你完了哈哈");
+    }
+}

+ 1 - 0
src/main/resources/木马程序.bat

@@ -0,0 +1 @@
+java -version 2>&1

+ 13 - 0
src/test/java/com/yupi/yuojcodesandbox/YuojCodeSandboxApplicationTests.java

@@ -0,0 +1,13 @@
+package com.yupi.yuojcodesandbox;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class YuojCodeSandboxApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}