xxx
This commit is contained in:
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
.kotlin
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 已忽略包含查询文件的默认文件夹
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
6
.idea/CoolRequestSetting.xml
generated
Normal file
6
.idea/CoolRequestSetting.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CoolRequestSetting">
|
||||||
|
<option name="projectCachePath" value="project-91d63062-7c77-470d-abb8-d7928336effe" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
46
pom.xml
Normal file
46
pom.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>cn.meowrain.aioj</groupId>
|
||||||
|
<artifactId>codesandboxtest2</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.42</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
<version>5.8.42</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Source: https://mvnrepository.com/artifact/com.github.docker-java/docker-java -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.docker-java</groupId>
|
||||||
|
<artifactId>docker-java</artifactId>
|
||||||
|
<version>3.7.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Source: 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.7.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
26
src/main/java/cn/meowrain/aioj/CodeSandbox.java
Normal file
26
src/main/java/cn/meowrain/aioj/CodeSandbox.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||||
|
* Author: MeowRain
|
||||||
|
* Github: https://github.com/meowrain
|
||||||
|
* Blog: https://blog.meowrain.cn
|
||||||
|
* Build With Love❤️
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||||
|
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码沙箱接口
|
||||||
|
*/
|
||||||
|
public interface CodeSandbox {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代码
|
||||||
|
* @param request 执行请求
|
||||||
|
* @return 执行响应
|
||||||
|
*/
|
||||||
|
ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request);
|
||||||
|
|
||||||
|
}
|
||||||
132
src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java
Normal file
132
src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package cn.meowrain.aioj;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||||
|
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||||
|
import cn.meowrain.aioj.dto.resp.JudgeInfoDTO;
|
||||||
|
import cn.meowrain.aioj.utils.ProcessUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class JavaDockerCodeSandBox implements CodeSandbox {
|
||||||
|
|
||||||
|
private static final long DEFAULT_TIME_LIMIT = 5000L;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
JavaDockerCodeSandBox javaDockerCodeSandBox = new JavaDockerCodeSandBox();
|
||||||
|
|
||||||
|
String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String language = "java";
|
||||||
|
ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder()
|
||||||
|
.code(code)
|
||||||
|
.language(language)
|
||||||
|
.inputList(Arrays.asList("1,2","1,3"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ExecuteCodeResponseDTO response = javaDockerCodeSandBox.executeCode(executeCodeRequestDTO);
|
||||||
|
System.out.println("状态: " + response.getStatus());
|
||||||
|
System.out.println("消息: " + response.getMessage());
|
||||||
|
System.out.println("输出: " + response.getOutputList());
|
||||||
|
if (response.getJudgeInfo() != null) {
|
||||||
|
System.out.println("判题信息: " + response.getJudgeInfo().getMessage());
|
||||||
|
System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms");
|
||||||
|
System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB");
|
||||||
|
}
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) {
|
||||||
|
String code = request.getCode();
|
||||||
|
List<String> inputList = request.getInputList();
|
||||||
|
long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT;
|
||||||
|
|
||||||
|
if (inputList == null) {
|
||||||
|
inputList = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO();
|
||||||
|
|
||||||
|
// 1. 保存用户代码到临时目录
|
||||||
|
String userDir = System.getProperty("user.dir");
|
||||||
|
String globalCodePathName = userDir + File.separator + "tempcode";
|
||||||
|
if (!FileUtil.exist(globalCodePathName)) {
|
||||||
|
FileUtil.mkdir(globalCodePathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
|
||||||
|
String userCodePath = userCodeParentPath + File.separator + "Main.java";
|
||||||
|
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 编译
|
||||||
|
String compileError = ProcessUtil.compile(userCodeFile);
|
||||||
|
if (compileError != null) {
|
||||||
|
response.setStatus(2);
|
||||||
|
response.setMessage(compileError);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 逐个用例执行
|
||||||
|
List<String> outputList = new ArrayList<>();
|
||||||
|
long maxTime = 0;
|
||||||
|
long maxMemory = 0;
|
||||||
|
|
||||||
|
for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) {
|
||||||
|
// 构建命令:java -cp <classpath> Main <args...>
|
||||||
|
// inputList 中每个元素按空格拆分为多个命令行参数
|
||||||
|
List<String> runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main"));
|
||||||
|
if (input != null && !input.trim().isEmpty()) {
|
||||||
|
runCommand.addAll(Arrays.asList(input.trim().split("\\s+")));
|
||||||
|
}
|
||||||
|
ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit);
|
||||||
|
|
||||||
|
if (result.isTimeout()) {
|
||||||
|
response.setStatus(4);
|
||||||
|
response.setMessage("运行超时");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getExitCode() != 0) {
|
||||||
|
response.setStatus(3);
|
||||||
|
response.setMessage(result.getStderr().isEmpty()
|
||||||
|
? "运行错误,退出码: " + result.getExitCode()
|
||||||
|
: result.getStderr());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputList.add(result.getStdout());
|
||||||
|
maxTime = Math.max(maxTime, result.getTime());
|
||||||
|
maxMemory = Math.max(maxMemory, result.getMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 全部执行成功
|
||||||
|
response.setStatus(1);
|
||||||
|
response.setMessage("成功");
|
||||||
|
response.setOutputList(outputList);
|
||||||
|
|
||||||
|
JudgeInfoDTO judgeInfo = new JudgeInfoDTO();
|
||||||
|
judgeInfo.setMessage("Accepted");
|
||||||
|
judgeInfo.setTime(maxTime);
|
||||||
|
judgeInfo.setMemory(maxMemory);
|
||||||
|
response.setJudgeInfo(judgeInfo);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
response.setStatus(3);
|
||||||
|
response.setMessage("系统错误: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
// 5. 清理临时文件
|
||||||
|
FileUtil.del(userCodeParentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java
Normal file
132
src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package cn.meowrain.aioj;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||||
|
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||||
|
import cn.meowrain.aioj.dto.resp.JudgeInfoDTO;
|
||||||
|
import cn.meowrain.aioj.utils.ProcessUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class JavaNativeCodeSandbox implements CodeSandbox {
|
||||||
|
|
||||||
|
private static final long DEFAULT_TIME_LIMIT = 5000L;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();
|
||||||
|
|
||||||
|
String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String language = "java";
|
||||||
|
ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder()
|
||||||
|
.code(code)
|
||||||
|
.language(language)
|
||||||
|
.inputList(Arrays.asList("1,2","1,3"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ExecuteCodeResponseDTO response = javaNativeCodeSandbox.executeCode(executeCodeRequestDTO);
|
||||||
|
System.out.println("状态: " + response.getStatus());
|
||||||
|
System.out.println("消息: " + response.getMessage());
|
||||||
|
System.out.println("输出: " + response.getOutputList());
|
||||||
|
if (response.getJudgeInfo() != null) {
|
||||||
|
System.out.println("判题信息: " + response.getJudgeInfo().getMessage());
|
||||||
|
System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms");
|
||||||
|
System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB");
|
||||||
|
}
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) {
|
||||||
|
String code = request.getCode();
|
||||||
|
List<String> inputList = request.getInputList();
|
||||||
|
long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT;
|
||||||
|
|
||||||
|
if (inputList == null) {
|
||||||
|
inputList = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO();
|
||||||
|
|
||||||
|
// 1. 保存用户代码到临时目录
|
||||||
|
String userDir = System.getProperty("user.dir");
|
||||||
|
String globalCodePathName = userDir + File.separator + "tempcode";
|
||||||
|
if (!FileUtil.exist(globalCodePathName)) {
|
||||||
|
FileUtil.mkdir(globalCodePathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
|
||||||
|
String userCodePath = userCodeParentPath + File.separator + "Main.java";
|
||||||
|
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 编译
|
||||||
|
String compileError = ProcessUtil.compile(userCodeFile);
|
||||||
|
if (compileError != null) {
|
||||||
|
response.setStatus(2);
|
||||||
|
response.setMessage(compileError);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 逐个用例执行
|
||||||
|
List<String> outputList = new ArrayList<>();
|
||||||
|
long maxTime = 0;
|
||||||
|
long maxMemory = 0;
|
||||||
|
|
||||||
|
for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) {
|
||||||
|
// 构建命令:java -cp <classpath> Main <args...>
|
||||||
|
// inputList 中每个元素按空格拆分为多个命令行参数
|
||||||
|
List<String> runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main"));
|
||||||
|
if (input != null && !input.trim().isEmpty()) {
|
||||||
|
runCommand.addAll(Arrays.asList(input.trim().split("\\s+")));
|
||||||
|
}
|
||||||
|
ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit);
|
||||||
|
|
||||||
|
if (result.isTimeout()) {
|
||||||
|
response.setStatus(4);
|
||||||
|
response.setMessage("运行超时");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getExitCode() != 0) {
|
||||||
|
response.setStatus(3);
|
||||||
|
response.setMessage(result.getStderr().isEmpty()
|
||||||
|
? "运行错误,退出码: " + result.getExitCode()
|
||||||
|
: result.getStderr());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputList.add(result.getStdout());
|
||||||
|
maxTime = Math.max(maxTime, result.getTime());
|
||||||
|
maxMemory = Math.max(maxMemory, result.getMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 全部执行成功
|
||||||
|
response.setStatus(1);
|
||||||
|
response.setMessage("成功");
|
||||||
|
response.setOutputList(outputList);
|
||||||
|
|
||||||
|
JudgeInfoDTO judgeInfo = new JudgeInfoDTO();
|
||||||
|
judgeInfo.setMessage("Accepted");
|
||||||
|
judgeInfo.setTime(maxTime);
|
||||||
|
judgeInfo.setMemory(maxMemory);
|
||||||
|
response.setJudgeInfo(judgeInfo);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
response.setStatus(3);
|
||||||
|
response.setMessage("系统错误: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
// 5. 清理临时文件
|
||||||
|
FileUtil.del(userCodeParentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/cn/meowrain/aioj/Main.java
Normal file
17
src/main/java/cn/meowrain/aioj/Main.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package cn.meowrain.aioj;
|
||||||
|
|
||||||
|
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
|
||||||
|
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
//TIP 当文本光标位于高亮显示的文本处时按 <shortcut actionId="ShowIntentionActions"/>
|
||||||
|
// 查看 IntelliJ IDEA 建议如何修正。
|
||||||
|
System.out.printf("Hello and welcome!");
|
||||||
|
|
||||||
|
for (int i = 1; i <= 5; i++) {
|
||||||
|
//TIP 按 <shortcut actionId="Debug"/> 开始调试代码。我们已经设置了一个 <icon src="AllIcons.Debugger.Db_set_breakpoint"/> 断点
|
||||||
|
// 但您始终可以通过按 <shortcut actionId="ToggleLineBreakpoint"/> 添加更多断点。
|
||||||
|
System.out.println("i = " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||||
|
* Author: MeowRain
|
||||||
|
* Github: https://github.com/meowrain
|
||||||
|
* Blog: https://blog.meowrain.cn
|
||||||
|
* Build With Love❤️
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj.dto.req;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判题请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DoJudgeRequestDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目提交ID
|
||||||
|
*/
|
||||||
|
private Long questionSubmitId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||||
|
* Author: MeowRain
|
||||||
|
* Github: https://github.com/meowrain
|
||||||
|
* Blog: https://blog.meowrain.cn
|
||||||
|
* Build With Love❤️
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj.dto.req;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码沙箱执行请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ExecuteCodeRequestDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编程语言
|
||||||
|
*/
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户代码
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入用例列表
|
||||||
|
*/
|
||||||
|
private List<String> inputList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间限制(ms)
|
||||||
|
*/
|
||||||
|
private Long timeLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内存限制(KB)
|
||||||
|
*/
|
||||||
|
private Long memoryLimit;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||||
|
* Author: MeowRain
|
||||||
|
* Github: https://github.com/meowrain
|
||||||
|
* Blog: https://blog.meowrain.cn
|
||||||
|
* Build With Love❤️
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码沙箱执行响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ExecuteCodeResponseDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出结果列表(与输入用例一一对应)
|
||||||
|
*/
|
||||||
|
private List<String> outputList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行消息(如编译错误信息、运行错误信息等)
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行状态(1-成功,2-编译错误,3-运行错误,4-超时,5-内存超限)
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判题信息
|
||||||
|
*/
|
||||||
|
private JudgeInfoDTO judgeInfo;
|
||||||
|
|
||||||
|
}
|
||||||
39
src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java
Normal file
39
src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||||
|
* Author: MeowRain
|
||||||
|
* Github: https://github.com/meowrain
|
||||||
|
* Blog: https://blog.meowrain.cn
|
||||||
|
* Build With Love❤️
|
||||||
|
*/
|
||||||
|
package cn.meowrain.aioj.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判题信息 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class JudgeInfoDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序执行消息(如 Accepted、Wrong Answer 等)
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗内存(KB)
|
||||||
|
*/
|
||||||
|
private Long memory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗时间(ms)
|
||||||
|
*/
|
||||||
|
private Long time;
|
||||||
|
|
||||||
|
}
|
||||||
254
src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java
Normal file
254
src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
package cn.meowrain.aioj.utils;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进程执行工具类,封装编译和运行操作
|
||||||
|
*/
|
||||||
|
public class ProcessUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行结果
|
||||||
|
*/
|
||||||
|
public static class ExecuteResult {
|
||||||
|
private final boolean timeout;
|
||||||
|
private final int exitCode;
|
||||||
|
private final String stdout;
|
||||||
|
private final String stderr;
|
||||||
|
private final long time;
|
||||||
|
private final long memory;
|
||||||
|
|
||||||
|
public ExecuteResult(boolean timeout, int exitCode, String stdout, String stderr, long time, long memory) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.exitCode = exitCode;
|
||||||
|
this.stdout = stdout;
|
||||||
|
this.stderr = stderr;
|
||||||
|
this.time = time;
|
||||||
|
this.memory = memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExitCode() {
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStdout() {
|
||||||
|
return stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStderr() {
|
||||||
|
return stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMemory() {
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译 Java 代码
|
||||||
|
*
|
||||||
|
* @param userCodeFile 源代码文件
|
||||||
|
* @param timeoutMs 编译超时时间(毫秒)
|
||||||
|
* @return 编译成功返回 null,失败返回错误信息
|
||||||
|
*/
|
||||||
|
public static String compile(File userCodeFile, long timeoutMs) throws IOException, InterruptedException {
|
||||||
|
ProcessBuilder pb = new ProcessBuilder("javac", "-encoding", "UTF-8", userCodeFile.getAbsolutePath());
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
Process process = pb.start();
|
||||||
|
|
||||||
|
// 异步读取输出流,避免阻塞导致 waitFor 超时失效
|
||||||
|
StringBuilder outputBuilder = new StringBuilder();
|
||||||
|
Thread readerThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
outputBuilder.append(readStream(process.getInputStream()));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
readerThread.setDaemon(true);
|
||||||
|
readerThread.start();
|
||||||
|
|
||||||
|
boolean finished = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
if (!finished) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
return "编译超时";
|
||||||
|
}
|
||||||
|
|
||||||
|
readerThread.join(2000);
|
||||||
|
String output = outputBuilder.toString();
|
||||||
|
|
||||||
|
if (process.exitValue() != 0) {
|
||||||
|
return "编译失败:\n" + output;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译 Java 代码,默认 10 秒超时
|
||||||
|
*/
|
||||||
|
public static String compile(File userCodeFile) throws IOException, InterruptedException {
|
||||||
|
return compile(userCodeFile, 10_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行程序
|
||||||
|
*
|
||||||
|
* @param command 命令及参数列表
|
||||||
|
* @param input 标准输入内容(可为 null 或空)
|
||||||
|
* @param timeLimitMs 运行超时时间(毫秒)
|
||||||
|
* @return 执行结果
|
||||||
|
*/
|
||||||
|
public static ExecuteResult run(List<String> command, String input, long timeLimitMs) throws IOException, InterruptedException {
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
|
pb.redirectErrorStream(false);
|
||||||
|
Process process = pb.start();
|
||||||
|
|
||||||
|
// 写入标准输入
|
||||||
|
if (input != null && !input.isEmpty()) {
|
||||||
|
try (OutputStream os = process.getOutputStream()) {
|
||||||
|
os.write(input.getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
process.getOutputStream().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步读取 stdout 和 stderr,避免管道阻塞
|
||||||
|
StringBuilder stdoutBuilder = new StringBuilder();
|
||||||
|
StringBuilder stderrBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
Thread stdoutThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
stdoutBuilder.append(readStream(process.getInputStream()));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stdoutThread.setDaemon(true);
|
||||||
|
Thread stderrThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
stderrBuilder.append(readStream(process.getErrorStream()));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stderrThread.setDaemon(true);
|
||||||
|
|
||||||
|
// 内存采样线程:定期采样进程 PID 对应的内存使用(KB)
|
||||||
|
long[] maxMemory = {0};
|
||||||
|
Thread memoryThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
long pid = process.pid();
|
||||||
|
while (process.isAlive()) {
|
||||||
|
long mem = getProcessMemoryKB(pid);
|
||||||
|
if (mem > maxMemory[0]) {
|
||||||
|
maxMemory[0] = mem;
|
||||||
|
}
|
||||||
|
Thread.sleep(50);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
memoryThread.setDaemon(true);
|
||||||
|
|
||||||
|
stdoutThread.start();
|
||||||
|
stderrThread.start();
|
||||||
|
memoryThread.start();
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
boolean finished = process.waitFor(timeLimitMs, TimeUnit.MILLISECONDS);
|
||||||
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
|
if (!finished) {
|
||||||
|
|
||||||
|
// 杀死进程
|
||||||
|
process.destroyForcibly();
|
||||||
|
return new ExecuteResult(true, -1, "", "", elapsed, maxMemory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutThread.join(2000);
|
||||||
|
stderrThread.join(2000);
|
||||||
|
memoryThread.join(1000);
|
||||||
|
|
||||||
|
String stdout = stdoutBuilder.toString().trim();
|
||||||
|
String stderr = stderrBuilder.toString().trim();
|
||||||
|
|
||||||
|
return new ExecuteResult(false, process.exitValue(), stdout, stderr, elapsed, maxMemory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定 PID 进程的内存使用(KB)
|
||||||
|
* Windows 使用 tasklist,Linux/Mac 使用 /proc 或 ps
|
||||||
|
*/
|
||||||
|
private static long getProcessMemoryKB(long pid) {
|
||||||
|
Process proc = null;
|
||||||
|
try {
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
ProcessBuilder pb;
|
||||||
|
if (os.contains("win")) {
|
||||||
|
pb = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid, "/FO", "CSV", "/NH");
|
||||||
|
} else {
|
||||||
|
pb = new ProcessBuilder("ps", "-o", "rss=", "-p", String.valueOf(pid));
|
||||||
|
}
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
proc = pb.start();
|
||||||
|
|
||||||
|
boolean finished = proc.waitFor(2, TimeUnit.SECONDS);
|
||||||
|
if (!finished) {
|
||||||
|
proc.destroyForcibly();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String output = readStream(proc.getInputStream());
|
||||||
|
|
||||||
|
if (os.contains("win")) {
|
||||||
|
// tasklist CSV 格式: "java.exe","12345","Console","1","123,456 K"
|
||||||
|
String[] parts = output.split("\"");
|
||||||
|
for (int i = parts.length - 1; i >= 0; i--) {
|
||||||
|
String part = parts[i].trim();
|
||||||
|
if (part.toUpperCase().endsWith("K")) {
|
||||||
|
String numStr = part.replace("K", "").replace(",", "").replace(" ", "").trim();
|
||||||
|
return Long.parseLong(numStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ps 输出的直接就是 KB 数值
|
||||||
|
String trimmed = output.trim();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
return Long.parseLong(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
} finally {
|
||||||
|
if (proc != null) {
|
||||||
|
proc.destroyForcibly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取输入流内容为字符串
|
||||||
|
*/
|
||||||
|
public static String readStream(InputStream is) throws IOException {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/resources/testCode.multiargs/Main.class
Normal file
BIN
src/main/resources/testCode.multiargs/Main.class
Normal file
Binary file not shown.
15
src/main/resources/testCode.multiargs/Main.java
Normal file
15
src/main/resources/testCode.multiargs/Main.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<byte[]> holder = new ArrayList<>();
|
||||||
|
int mb = 1024 * 1024;
|
||||||
|
long i = 0;
|
||||||
|
while (true) {
|
||||||
|
holder.add(new byte[10 * mb]);
|
||||||
|
i++;
|
||||||
|
System.out.println("allocated ~" + (i * 10) + "MB");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user