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

321 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.guwan.backend.controller;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.*;
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;
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;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
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.");
}
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;
}
}
}