判题模块
2025/5/19大约 5 分钟
判题机的部署
项目地址: https://github.com/judge0/judge0/releases/tag/v1.13.1
根据给出的docker compose文件和配置文件来进行部署
cd judge0-v1.13.1
docker-compose up -d db redis
sleep 10s
docker-compose up -d
sleep 5s
然后访问http://<IP ADDRESS OF YOUR SERVER>:2358/docs
可查看文档

判题模块的实现
判题接口定义
定义接口
@RequiredArgsConstructor
@RequestMapping
@RestController
@Slf4j
@Tag(name = "判题相关接口")
public class JudgeController {
private final JudgeService judgeService;
@Operation(summary = "判题操作")
@PostMapping("/judge")
public Result<Long> judge(@RequestBody JudgeDTO judgeDTO) {
Long submissionId;
try {
submissionId = judgeService.judge(judgeDTO);
} catch (Exception e) {
return Result.error("判题服务异常");
}
return Result.success(submissionId);
}
}
向后端传递以下信息:

判题函数实现
- 这是本项目最核心的模块!!!
首先向这个函数传递源代码,代码语言id,以及内存,运行时间等限制,然后传递输入输出,通过使用线程池创建任务来实现多线程判题,判题流程如下:
首先和判题机建立链接 将判题参数转为json格式
向判题机发送判题请求,同步等待判题机返回判题token
然后使用轮询的方法检查是否判题完成,如果未完成,则重新发送请求判断,
其中对相关属性的非空判断尤为重要!!!
判断判题是否通过,如果未通过需要将判题时间和内存设置为0,
否则将出现空指针异常
最后关闭链接,返回结果
public JudgeResult simpleJudge(JudgeDomain judge, String input, String output) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(configuration.getURL() + "/submissions");
judge.setStdin(input);
judge.setExpected_output(output);
CloseableHttpResponse response;
String json = JSON.toJSONString(judge);
StringEntity entity = new StringEntity(json);
entity.setContentEncoding("utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
HttpEntity entity1 = response.getEntity();
String body = EntityUtils.toString(entity1);
JudgeToken parse = JSON.parseObject(body, JudgeToken.class);
HttpGet httpGet = new HttpGet(configuration.getURL() + "/submissions/" + parse.getToken());
JudgeResult result = null;
for (int i = 0; i < 10; i++) {
Thread.sleep(600);
CloseableHttpResponse execute = httpClient.execute(httpGet);
HttpEntity entity2 = execute.getEntity();
String jsonResult = EntityUtils.toString(entity2);
result = JSON.parseObject(jsonResult, JudgeResult.class);
if (result == null || result.getStatus() == null) {
continue;
}
if (!(result.getStatus().getId().equals(JudgeResultConstant.InQueue) || result.getStatus().getId().equals(JudgeResultConstant.Processing))) {
break;
}
}
if (result != null) {
if (result.getMemory() == null) {
result.setMemory(0);
}
if (result.getTime() == null) {
result.setTime(0.0);
}
}
response.close();
httpClient.close();
return result;
}
非竞赛判题
此处逻辑较为复杂,我已给出每步操作的注释,请仔细阅读!!!
@Transactional
public Long judge(JudgeDTO judgeDTO) {
//获取题目信息
Problem problem = problemService.getById(judgeDTO.getProblemId());
List<JudgePoint> judgePointList = judgePointService.lambdaQuery().
eq(JudgePoint::getProblemId, judgeDTO.getProblemId()).
list();
//设置latch便于等待多线程判题结束
CountDownLatch latch = new CountDownLatch(judgePointList.size());
//保存本次提交记录,获取id
Submission submission = new Submission();
submission.setCreateTime(LocalDateTime.now());
submission.setUserId(CurrentIdUtils.getCurrentId());
submission.setProblemId(judgeDTO.getProblemId());
submissionService.save(submission);
//设置判题限制
JudgeDomain dto = JudgeDomain.builder()
.source_code(judgeDTO.getCode())
.language_id(judgeDTO.getLanguage())
.memory_limit(problem.getMemoryLimit() * 1024)
.stack_limit(problem.getStackLimit())
.cpu_time_limit(problem.getTimeLimit() / 1000.0)
.build();
//创建判题结果队列
CopyOnWriteArrayList<JudgeResult> results = new CopyOnWriteArrayList<>();
//开始判题
try {
for (JudgePoint judgePoint : judgePointList) {
executor.execute(() -> {
try {
JudgeResult result = simpleJudge(dto, judgePoint.getInput(), judgePoint.getOutput());
//设置测试点id和测试点输入值
result.setPointId(judgePoint.getId());
result.setInput(judgePoint.getInput());
results.add(result);
latch.countDown();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
int full = 0;
int score = 0;
int judgeTime = 0;
int judgeMemory = 0;
int judgeResult = JudgeResultConstant.Accepted;
boolean flag = true;
ArrayList<SubPoint> subPointList = new ArrayList<>();
for (JudgeResult result : results) {
//将测试点数据加入列表以便批量插入
SubPoint subPoint = SubPoint.builder()
.submissionId(submission.getId()).judgePointId(result.getPointId())
.codeLanguage(judgeDTO.getLanguage()).judgeResult(result.getStatus().getId())
.judgeMemory(result.getMemory()).judgeTime((int) (result.getTime() * 1000))
.input(result.getInput()).output(result.getStdout())
.build();
subPointList.add(subPoint);
//计算分数
full++;
if (subPoint.getJudgeResult().equals(JudgeResultConstant.Accepted)) {
score++;
}
//记录内存和时间(多个测试点的最大值)
judgeTime = Integer.max(judgeTime, subPoint.getJudgeTime());
judgeMemory = Integer.max(judgeMemory, subPoint.getJudgeMemory());
//记录判题总结果(如果有测试点没有编译通过则编译失败,通过有测试点不成功则失败)
if (flag && subPoint.getJudgeResult().equals(JudgeResultConstant.CompilationError)) {
judgeResult = JudgeResultConstant.CompilationError;
flag = false;
}
if (flag && !subPoint.getJudgeResult().equals(JudgeResultConstant.Accepted)) {
judgeResult = subPoint.getJudgeResult();
flag = false;
}
}
//完善提交记录
submission.setCodeLanguage(CodeLanguageConstant.getLanguageName(judgeDTO.getLanguage()));
submission.setJudgeScore((int) ((score * 1.0) / (full * 1.0) * 100));
submission.setCode(judgeDTO.getCode());
submission.setJudgeResult(judgeResult);
submission.setJudgeTime(judgeTime);
submission.setJudgeMemory(judgeMemory);
//保存到数据库中
submissionService.updateById(submission);
subPointService.saveBatch(subPointList);
if (full == score) {
problemService.increaseAC(problem.getId());
} else {
problemService.increase(problem.getId());
}
return submission.getId();
}
@Update("update problem set sub_number = sub_number + 1 where id = #{id}")
void increase(Long id);
@Update("update problem set sub_number = sub_number + 1 ,ac_number = ac_number + 1 where id = #{id}")
void increaseAC(Long id);
判题相关常量
判题机ip地址
在yaml文件里定义:
judge: URL: http://192.168.154.128:2358
然后定义相关类读取配置
@Data @Component @ConfigurationProperties(prefix = "judge") public class JudgeConfiguration { private String URL; }
判题语言定义
public class CodeLanguageConstant { public static HashMap<Integer, String> languageMap = new HashMap<>(); static { languageMap.put(45, "Assembly (NASM 2.14.02)"); languageMap.put(46, "Bash (5.0.0)"); languageMap.put(47, "Basic (FBC 1.07.1)"); languageMap.put(75, "C (Clang 7.0.1)"); languageMap.put(76, "C++ (Clang 7.0.1)"); languageMap.put(48, "C (GCC 7.4.0)"); languageMap.put(52, "C++ (GCC 7.4.0)"); languageMap.put(49, "C (GCC 8.3.0)"); languageMap.put(53, "C++ (GCC 8.3.0)"); languageMap.put(50, "C (GCC 9.2.0)"); languageMap.put(54, "C++ (GCC 9.2.0)"); languageMap.put(86, "Clojure (1.10.1)"); languageMap.put(51, "C# (Mono 6.6.0.161)"); languageMap.put(77, "COBOL (GnuCOBOL 2.2)"); languageMap.put(55, "Common Lisp (SBCL 2.0.0)"); languageMap.put(56, "D (DMD 2.089.1)"); languageMap.put(57, "Elixir (1.9.4)"); languageMap.put(58, "Erlang (OTP 22.2)"); languageMap.put(44, "Executable"); languageMap.put(87, "F# (.NET Core SDK 3.1.202)"); languageMap.put(59, "Fortran (GFortran 9.2.0)"); languageMap.put(60, "Go (1.13.5)"); languageMap.put(88, "Groovy (3.0.3)"); languageMap.put(61, "Haskell (GHC 8.8.1)"); languageMap.put(62, "Java (OpenJDK 13.0.1)"); languageMap.put(63, "JavaScript (Node.js 12.14.0)"); languageMap.put(78, "Kotlin (1.3.70)"); languageMap.put(64, "Lua (5.3.5)"); languageMap.put(89, "Multi-file program"); languageMap.put(79, "Objective-C (Clang 7.0.1)"); languageMap.put(65, "OCaml (4.09.0)"); languageMap.put(66, "Octave (5.1.0)"); languageMap.put(67, "Pascal (FPC 3.0.4)"); languageMap.put(85, "Perl (5.28.1)"); languageMap.put(68, "PHP (7.4.1)"); languageMap.put(43, "Plain Text"); languageMap.put(69, "Prolog (GNU Prolog 1.4.5)"); languageMap.put(70, "Python (2.7.17)"); languageMap.put(71, "Python (3.8.1)"); languageMap.put(80, "R (4.0.0)"); languageMap.put(72, "Ruby (2.7.0)"); languageMap.put(73, "Rust (1.40.0)"); languageMap.put(81, "Scala (2.13.2)"); languageMap.put(82, "SQL (SQLite 3.27.2)"); languageMap.put(83, "Swift (5.2.3)"); languageMap.put(74, "TypeScript (3.7.4)"); languageMap.put(84, "Visual Basic.Net (vbnc 0.0.0.5943)"); } public static String getLanguageName(Integer code){ return languageMap.get(code); } }
判题结果定义
public class JudgeResultConstant {
public static final Integer InQueue = 1;
public static final Integer Processing = 2;
public static final Integer Accepted = 3;
public static final Integer WrongAnswer = 4;
public static final Integer TimeLimitExceeded = 5;
public static final Integer CompilationError = 6;
public static final Integer RuntimeErrorSIGSEGV = 7;
public static final Integer RuntimeErrorSIGXFSZ=8;
public static final Integer RuntimeErrorSIGFPE=9;
public static final Integer RuntimeErrorSIGABRT=10;
public static final Integer RuntimeErrorNZEC=11;
public static final Integer RuntimeErrorOther=12;
public static final Integer InternalError=13;
public static final Integer ExecFormatError=14;
public static final Map<Integer, String> RESULTS = new HashMap<>();
static {
RESULTS.put(InQueue, "In Queue");
RESULTS.put(Processing, "Processing");
RESULTS.put(Accepted, "Accepted");
RESULTS.put(WrongAnswer, "Wrong Answer");
RESULTS.put(TimeLimitExceeded, "Time Limit Exceeded");
RESULTS.put(CompilationError, "Compilation Error");
RESULTS.put(RuntimeErrorSIGSEGV, "Runtime Error SIGSEGV");
RESULTS.put(RuntimeErrorSIGXFSZ, "Runtime Error SIGXFSZ");
RESULTS.put(RuntimeErrorSIGFPE, "Runtime Error SIGFPE");
RESULTS.put(RuntimeErrorSIGABRT, "Runtime Error SIGABRT");
RESULTS.put(RuntimeErrorNZEC, "Runtime Error NZEC");
RESULTS.put(RuntimeErrorOther, "Runtime Error Other");
RESULTS.put(InternalError, "Internal Error");
RESULTS.put(ExecFormatError, "Exec Format Error");
}
public static String getResultById(Integer id){
return RESULTS.get(id);
}
}
判题相关流程

相关实体类的定义
@Builder
@Data
public class JudgeDomain {
private String source_code;
private Integer language_id;
private Integer number_of_runs;
private String stdin;
private String expected_output;
private Double cpu_time_limit;
private Integer cpu_extra_time;
private Integer wall_time_limit;
private Integer memory_limit;
private Integer stack_limit;
private Integer max_processes_and_or_threads;
private Integer enable_per_process_and_thread_time_limit;
private Integer enable_per_process_and_thread_memory_limit;
private Integer max_file_size;
private Integer enable_network;
}
@Data
public class JudgeDTO {
private Long problemId;
private Integer language;
private String code;
}
@Data
public class JudgeResult {
private String stdout;
private String input;
private Long pointId;
private Double time;
private Integer memory;
private String stderr;
private String token;
private String compile_output;
private String message;
private Status status;
}
@Data
public class JudgeToken {
private String token;
}
@Builder
@Data
public class JudgeVO {
private Integer time;
private Integer memory;
private String status;
private Long judgePointId;
}
@Data
@AllArgsConstructor
public class Status {
private Integer id;
private String description;
}