yl-backend/src/main/java/com/guwan/backend/controller/DockerController.java

321 lines
12 KiB
Java
Raw Normal View History

package com.guwan.backend.controller;
import com.github.dockerjava.api.command.CreateContainerCmd;
2025-03-30 20:32:16 +08:00
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.*;
2025-03-30 20:32:16 +08:00
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
2025-03-30 20:32:16 +08:00
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.github.dockerjava.api.DockerClient;
2025-03-30 20:32:16 +08:00
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
2025-03-30 20:32:16 +08:00
import java.util.*;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/common")
public class DockerController {
private final DockerClient dockerClient;
@Autowired
public DockerController(DockerClient dockerClient) {
this.dockerClient = dockerClient;
}
@GetMapping("/docker-info")
public String getDockerInfo() {
Info info = dockerClient.infoCmd().exec(); // 获取 Docker 守护进程的信息
System.out.println("info = " + info);
return info.toString(); // 将 Docker 信息返回到 HTTP 响应
}
/**
* 获取镜像列表
*
* @param
* @return
*/
@GetMapping("/imageList")
public List<Image> imageList() {
List<Image> imageList = dockerClient.listImagesCmd().withShowAll(true).exec();
System.out.println("imageList = " + imageList);
return imageList;
}
@GetMapping("/listContainers")
public List<Container> listContainers() {
List<Container> exec = dockerClient.listContainersCmd().exec();
exec.forEach(container ->
System.out.println("name = " + Arrays.toString(container.getNames())));
return exec;
}
@GetMapping("/create")
public void create() throws IOException {
String javaFilePath = "D:\\00_桌面\\demo.java"; // 这里填入 Java 文件路径
File javaFile = new File(javaFilePath);
Path localDir = Files.createTempDirectory("docker-java");
File localJavaFile = new File(localDir.toFile(), "Main.java");
Files.copy(javaFile.toPath(), localJavaFile.toPath());
String imageName = "openjdk:8"; // 使用官方 OpenJDK 11 镜像
HostConfig hostConfig = HostConfig.newHostConfig()
.withBinds(new Bind(localDir.toString(), new com.github.dockerjava.api.model.Volume("/java"))); // 绑定挂载
// 启动容器
String containerId = dockerClient.createContainerCmd(imageName)
.withCmd("bash", "-c", "javac /java/Main.java && java -cp /java Main && tail -f /dev/null")
.withHostConfig(hostConfig)
.withName("java-app-container")
.exec().getId();
dockerClient.startContainerCmd(containerId).exec();
System.out.println("Container started. Check the output in Docker.");
}
2025-03-30 20:32:16 +08:00
private static final String IMAGE_NAME = "gcc:latest";
private static final int TIMEOUT_SECONDS = 10;
/* @GetMapping("/testCpp")
public void testCpp() throws IOException {
System.out.println("dockerClient = " + dockerClient);
try {
// 创建容器
CreateContainerResponse container = dockerClient.createContainerCmd(IMAGE_NAME)
.withTty(true)
.withWorkingDir("/app")
.exec();
String containerId = container.getId();
dockerClient.startContainerCmd(containerId).exec();
// 写入代码使用cat和here-doc避免转义问题
String cppCode = "#include <iostream>\n"
+ "int main() { std::cout << \"Hello Docker!\\n\"; return 0; }";
execCommand(dockerClient, containerId,
"sh -c 'cat > /app/main.cpp <<EOF\n" + cppCode + "\nEOF'");
// 编译代码(使用相对路径)
execCommand(dockerClient, containerId, "g++ main.cpp -o main");
// 执行程序(使用相对路径)
String output = execCommand(dockerClient, containerId, "./main");
System.out.println("程序输出: " + output);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 清理容器
*//*dockerClient.listContainersCmd().withShowAll(true).exec().forEach(c ->
dockerClient.removeContainerCmd(c.getId()).withForce(true).exec());*//*
//dockerClient.close();
}
}
private static String execCommand(DockerClient dockerClient, String containerId, String command)
throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
.withCmd("sh", "-c", command)
.withAttachStdout(true)
.withAttachStderr(true)
.exec();
dockerClient.execStartCmd(exec.getId())
.exec(new ExecStartResultCallback(outputStream, System.err))
.awaitCompletion(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return outputStream.toString().trim();
}*/
@GetMapping("/judge")
public ResponseEntity<Map<String, Object>> judgeCode() {
Map<String, Object> result = new HashMap<>();
List<TestCase> testCases = new ArrayList<>();
// 真实用户提交的C++代码示例A+B问题
String cppCode =
"#include <iostream>\n" +
"using namespace std;\n" +
"int main() {\n" +
" int a, b;\n" +
" cin >> a >> b;\n" +
" cout << a + b << endl;\n" +
" return 0;\n" +
"}";
// 定义测试用例
testCases.add(new TestCase("1 2", "3")); // 基础测试
testCases.add(new TestCase("-5 8", "3")); // 负数测试
testCases.add(new TestCase("1000000 2000000", "3000000")); // 大数测试
testCases.add(new TestCase("", "")); // 错误输入测试
testCases.add(new TestCase("3", "")); // 不完整输入测试
String containerId = null;
try {
// 创建容器使用官方GCC镜像
CreateContainerResponse container = dockerClient.createContainerCmd("gcc:latest")
.withTty(true)
.withWorkingDir("/app")
.withHostConfig(HostConfig.newHostConfig()
.withMemory(100 * 1024 * 1024L) // 限制内存100MB
.withCpuCount(1L)) // 限制1核CPU
.exec();
containerId = container.getId();
dockerClient.startContainerCmd(containerId).exec();
// 1. 写入代码文件
execCommand(containerId, "mkdir -p /app", 10);
execCommand(containerId,
"bash -c 'cat > /app/main.cpp <<EOF\n" +
cppCode.replace("'", "'\"'\"'") + // 处理单引号
"\nEOF'", 10);
// 2. 编译代码(带编译错误检查)
String compileOutput = execCommand(containerId, "g++ main.cpp -o main -O2 -Wall", 10);
if (!compileOutput.isEmpty()) {
result.put("status", "compile_error");
result.put("message", compileOutput);
return ResponseEntity.ok(result);
}
// 3. 执行测试用例
List<Map<String, Object>> caseResults = new ArrayList<>();
for (int i = 0; i < testCases.size(); i++) {
TestCase testCase = testCases.get(i);
Map<String, Object> caseResult = new HashMap<>();
caseResult.put("case_id", i + 1);
caseResult.put("input", testCase.input);
caseResult.put("expected", testCase.expectedOutput);
try {
// 写入输入文件
execCommand(containerId,
"bash -c 'cat > /app/input.txt <<EOF\n" +
testCase.input.replace("'", "'\"'\"'") +
"\nEOF'", 10);
// 执行程序(带超时控制)
String actualOutput = execCommand(containerId,
"timeout 2s ./main < /app/input.txt 2>&1", // 捕获标准错误
3 // 超时3秒
);
// 标准化输出处理
actualOutput = actualOutput.trim()
.replaceAll("\r\n", "\n")
.replaceAll("[ \\t]+", " ");
// 结果对比
boolean isPassed = actualOutput.equals(testCase.expectedOutput);
caseResult.put("status", isPassed ? "passed" : "failed");
caseResult.put("actual", actualOutput);
// 特殊错误类型检测
if (actualOutput.contains("Timeout")) {
caseResult.put("status", "time_limit_exceeded");
} else if (actualOutput.contains("runtime error")) {
caseResult.put("status", "runtime_error");
} else if (actualOutput.isEmpty()) {
caseResult.put("status", "no_output");
}
} catch (Exception e) {
caseResult.put("status", "system_error");
caseResult.put("message", e.getMessage());
}
caseResults.add(caseResult);
}
result.put("status", "completed");
result.put("cases", caseResults);
result.put("pass_count", caseResults.stream()
.filter(c -> "passed".equals(c.get("status")))
.count());
} catch (Exception e) {
result.put("status", "system_error");
result.put("message", e.getMessage());
} finally {
if (containerId != null) {
dockerClient.removeContainerCmd(containerId)
.withForce(true)
.exec();
}
}
return ResponseEntity.ok(result);
}
// 辅助方法:执行容器命令
private String execCommand(String containerId, String command, int timeoutSeconds)
throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
.withCmd("bash", "-c", command)
.withAttachStdout(true)
.withAttachStderr(true)
.exec();
dockerClient.execStartCmd(exec.getId())
.exec(new ExecStartResultCallback(output, output))
.awaitCompletion(timeoutSeconds, TimeUnit.SECONDS);
return output.toString().trim();
}
// 测试用例类
static class TestCase {
String input;
String expectedOutput;
public TestCase(String input, String expectedOutput) {
this.input = input;
this.expectedOutput = expectedOutput;
}
}
}