提交代码

This commit is contained in:
2026-02-12 20:54:28 +08:00
parent b9849070bb
commit e4a33b630e
8 changed files with 278 additions and 15 deletions

View File

@@ -1,7 +1,8 @@
{
"permissions": {
"allow": [
"Bash(del \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\main\\\\java\\\\cn\\\\meowrain\\\\aioj\\\\JavaDockerCodeSandBox.java\")"
"Bash(del \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\main\\\\java\\\\cn\\\\meowrain\\\\aioj\\\\JavaDockerCodeSandBox.java\")",
"Bash(if not exist \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\test\\\\java\\\\cn\\\\meowrain\\\\aioj\" mkdir \"C:\\\\Users\\\\meowr\\\\IdeaProjects\\\\codesandboxtest2\\\\src\\\\test\\\\java\\\\cn\\\\meowrain\\\\aioj\")"
]
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CoolRequestCommonStatePersistent">
<option name="searchCache" value="filter" />
</component>
</project>

15
.idea/deployment.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" autoUpload="Always" serverName="vmware" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="vmware">
<serverdata>
<mappings>
<mapping deploy="/" local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
<option name="myAutoUpload" value="ALWAYS" />
</component>
</project>

14
.idea/webServers.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="a14712e8-ebb3-463f-86fe-e45b23beb955" name="vmware">
<fileTransfer rootFolder="/home/meowrain/projects" accessType="SFTP" host="192.168.42.129" port="22" sshConfigId="fd54d56e-f791-498f-a842-bea367fa42f7" sshConfig="meowrain@192.168.42.129:22 password">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

View File

@@ -42,5 +42,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -14,6 +14,7 @@ import com.github.dockerjava.api.command.ExecCreateCmdResponse; // 创建 exec
import com.github.dockerjava.api.command.InspectContainerResponse; // 容器检查响应
import com.github.dockerjava.api.command.InspectExecResponse; // exec 检查响应
import com.github.dockerjava.api.command.PullImageResultCallback; // 拉取镜像回调
import com.github.dockerjava.api.exception.NotFoundException; // 镜像/容器不存在异常
import com.github.dockerjava.api.model.*; // Docker 模型类
import com.github.dockerjava.core.DefaultDockerClientConfig; // Docker 客户端默认配置
import com.github.dockerjava.core.DockerClientConfig; // Docker 客户端配置接口
@@ -108,10 +109,17 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
@Override
protected RunResult runCase(String input, String codePath, LanguageConfig config,
long timeLimitMs, long memoryLimitKB) throws Exception {
// 将测试输入写入 input.txt(容器内通过 cat 读取)
// 将测试输入写入 input.txt
String inputFilePath = codePath + File.separator + "input.txt"; // input.txt 宿主机路径
FileUtil.writeString(input != null ? input : "", inputFilePath, StandardCharsets.UTF_8); // 写入输入内容
// 使用 docker cp 将 input.txt 复制到容器内 /app
DockerClient client = getDockerClient();
client.copyArchiveToContainerCmd(containerId)
.withHostResource(inputFilePath) // 宿主机文件路径
.withRemotePath(CONTAINER_WORK_DIR) // 容器内目标路径
.exec();
// 计算 JVM 堆内存限制(容器内存的 80%,最低 16MB
long xmxMB = Math.max(16, (memoryLimitKB / 1024) * 80 / 100);
String[] runCmd = config.getRunCmd(xmxMB); // 获取运行命令(替换内存占位符)
@@ -168,10 +176,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
synchronized (DockerCodeSandbox.class) { // 加锁防止并发拉取
if (readyImages.contains(image)) return; // 双重检查
DockerClient client = getDockerClient(); // 获取客户端
List<Image> images = client.listImagesCmd() // 查询本地镜像
.withImageNameFilter(image) // 按名称过滤
.exec(); // 执行查询
if (images.isEmpty()) { // 本地不存在
boolean exists;
try {
client.inspectImageCmd(image).exec(); // 精确查找镜像
exists = true;
} catch (NotFoundException e) { // 镜像不存在
exists = false;
}
if (!exists) { // 本地不存在
System.out.println("拉取镜像: " + image); // 打印提示
try {
client.pullImageCmd(image) // 拉取镜像
@@ -198,7 +210,6 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
// 配置宿主机资源限制和安全选项
HostConfig hostConfig = HostConfig.newHostConfig()
.withBinds(new Bind(hostCodePath, new Volume(CONTAINER_WORK_DIR))) // 挂载代码目录到 /app
.withMemory(memoryBytes) // 内存硬限制
.withMemorySwap(memoryBytes) // 禁用 swap
.withCpuCount(1L) // 限制 1 个 CPU
@@ -219,6 +230,14 @@ public class DockerCodeSandbox extends AbstractCodeSandbox {
String id = container.getId(); // 获取容器 ID
client.startContainerCmd(id).exec(); // 启动容器
// 使用 docker cp 将代码文件复制到容器 /app替代 bind mount兼容远程 Docker / WSL2
client.copyArchiveToContainerCmd(id)
.withHostResource(hostCodePath) // 宿主机代码目录
.withRemotePath(CONTAINER_WORK_DIR) // 容器内目标路径
.withDirChildrenOnly(true) // 只复制目录内容,不含目录本身
.exec();
return id; // 返回容器 ID
}

View File

@@ -3,13 +3,6 @@ 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");
}
System.out.println("helloworld ");
}
}

View File

@@ -0,0 +1,208 @@
package cn.meowrain.aioj;
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class DockerCodeSandboxTest {
private DockerCodeSandbox sandbox;
@BeforeEach
void setUp() {
sandbox = new DockerCodeSandbox();
}
// ==================== Java ====================
@Test
void testJava_HelloWorld() {
String code = """
public class Main {
public static void main(String[] args) {
System.out.println("hello java");
}
}
""";
ExecuteCodeResponseDTO resp = execute("java", code, List.of(""));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals("hello java", resp.getOutputList().get(0));
}
@Test
void testJava_ReadStdin() {
String code = """
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(), b = sc.nextInt();
System.out.println(a + b);
}
}
""";
ExecuteCodeResponseDTO resp = execute("java", code, Arrays.asList("1 2", "10 20"));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals(List.of("3", "30"), resp.getOutputList());
}
@Test
void testJava_CompileError() {
String code = """
public class Main {
public static void main(String[] args) {
System.out.println("missing quote)
}
}
""";
ExecuteCodeResponseDTO resp = execute("java", code, List.of(""));
assertEquals(2, resp.getStatus(), "should be compile error");
}
// ==================== Python ====================
@Test
void testPython_HelloWorld() {
String code = """
print("hello python")
""";
ExecuteCodeResponseDTO resp = execute("python", code, List.of(""));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals("hello python", resp.getOutputList().get(0));
}
@Test
void testPython_ReadStdin() {
String code = """
a, b = map(int, input().split())
print(a + b)
""";
ExecuteCodeResponseDTO resp = execute("python", code, Arrays.asList("3 4", "100 200"));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals(List.of("7", "300"), resp.getOutputList());
}
@Test
void testPython_RuntimeError() {
String code = """
print(1 / 0)
""";
ExecuteCodeResponseDTO resp = execute("python", code, List.of(""));
assertEquals(3, resp.getStatus(), "should be runtime error");
}
// ==================== Go ====================
@Test
void testGo_HelloWorld() {
String code = """
package main
import "fmt"
func main() {
fmt.Println("hello go")
}
""";
ExecuteCodeResponseDTO resp = execute("go", code, List.of(""));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals("hello go", resp.getOutputList().get(0));
}
@Test
void testGo_ReadStdin() {
String code = """
package main
import "fmt"
func main() {
var a, b int
fmt.Scan(&a, &b)
fmt.Println(a + b)
}
""";
ExecuteCodeResponseDTO resp = execute("go", code, Arrays.asList("5 6", "50 60"));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals(List.of("11", "110"), resp.getOutputList());
}
@Test
void testGo_CompileError() {
String code = """
package main
func main() {
x := 1
}
""";
ExecuteCodeResponseDTO resp = execute("go", code, List.of(""));
assertEquals(2, resp.getStatus(), "should be compile error (unused variable)");
}
// ==================== JavaScript ====================
@Test
void testJavaScript_HelloWorld() {
String code = """
console.log("hello js");
""";
ExecuteCodeResponseDTO resp = execute("javascript", code, List.of(""));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals("hello js", resp.getOutputList().get(0));
}
@Test
void testJavaScript_ReadStdin() {
String code = """
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const [a, b] = line.split(' ').map(Number);
console.log(a + b);
rl.close();
});
""";
ExecuteCodeResponseDTO resp = execute("javascript", code, Arrays.asList("7 8", "70 80"));
assertEquals(1, resp.getStatus(), resp.getMessage());
assertEquals(List.of("15", "150"), resp.getOutputList());
}
@Test
void testJavaScript_RuntimeError() {
String code = """
undefined.foo();
""";
ExecuteCodeResponseDTO resp = execute("javascript", code, List.of(""));
assertEquals(3, resp.getStatus(), "should be runtime error");
}
// ==================== 边界场景 ====================
@Test
void testUnsupportedLanguage() {
ExecuteCodeResponseDTO resp = execute("rust", "fn main() {}", List.of(""));
assertEquals(3, resp.getStatus());
assertTrue(resp.getMessage().contains("不支持的语言"));
}
// ==================== 辅助方法 ====================
private ExecuteCodeResponseDTO execute(String language, String code, List<String> inputList) {
ExecuteCodeRequestDTO request = ExecuteCodeRequestDTO.builder()
.language(language)
.code(code)
.inputList(inputList)
.build();
ExecuteCodeResponseDTO resp = sandbox.executeCode(request);
System.out.printf("[%s] status=%d, message=%s, output=%s%n",
language, resp.getStatus(), resp.getMessage(), resp.getOutputList());
if (resp.getJudgeInfo() != null) {
System.out.printf(" -> time=%dms, memory=%dKB%n",
resp.getJudgeInfo().getTime(), resp.getJudgeInfo().getMemory());
}
return resp;
}
}