add interactive judge and Reconstruct judge mode

This commit is contained in:
Himit_ZH 2022-01-04 20:40:57 +08:00
parent 994b05e8db
commit 67fe2efbb4
48 changed files with 2211 additions and 1090 deletions

View File

@ -10,7 +10,7 @@
## 一、前言
基于前后端分离分布式架构的在线测评平台hoj前端使用vue后端主要使用springbootredismysqlnacos等技术**支持HDU、POJ、Codeforces包括GYM的vjudge判题同时适配手机端、电脑端浏览拥有讨论区与站内消息系统支持私有训练、公开训练题单还有完善的比赛功能打星队伍、关注队伍、外榜。**
基于前后端分离分布式架构的在线测评平台hoj前端使用vue后端主要使用springbootredismysqlnacos等技术**支持HDU、POJ、Codeforces包括GYM的vjudge判题同时适配手机端、电脑端浏览拥有讨论区与站内消息系统支持私有训练、公开训练题单还有完善的判题模式(普通测评、特殊测评、交互测评)和完善的比赛功能(打星队伍、关注队伍、外榜)。**
| 在线Demo | 在线文档 | Github&Gitee仓库地址 | QQ群 |
| :--------------------------------: | :-------------------------------------------------------: | :----------------------------------------------------------: | :-------: |
@ -98,6 +98,7 @@ docker ps # 查看当前运行的容器状态
| 2021-10-06 | 美化比赛排行榜增加对FPS题目导入的支持 | Himit_ZH |
| 2021-12-09 | 美化比赛排行榜,增加外榜、打星队伍、关注队伍的支持 | Himit_ZH |
| 2022-01-01 | 增加公开训练和公开训练(题单) | Himit_ZH |
| 2022-01-04 | 增加交互判题、重构judgeserver的三种判题模式普通、特殊、交互 | Himit_ZH |
## 五、部分截图

View File

@ -89,6 +89,7 @@ module.exports = context => config({
collapsable: true,
children: [
'use/import-problem',
'use/judge-mode',
'use/testcase',
'use/training',
'use/contest',
@ -97,8 +98,7 @@ module.exports = context => config({
'use/notice-announcement',
'use/discussion-admin',
'use/update-fe',
'use/close-free-cdn',
'use/spj'
'use/close-free-cdn'
]
},
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -20,7 +20,7 @@ features:
details: 判题使用 cgroup 隔离用户程序,网站权限控制完善
- title: 多样化
details: 独有自身判题服务同时支持其它知名OJ题目的提交判题
footer: MIT Licensed | Copyright © 2022.01.01 @Author Himit_ZH QQ Group:598587305
footer: MIT Licensed | Copyright © 2022.01.04 @Author Himit_ZH QQ Group:598587305
---
[![Java](https://img.shields.io/badge/Java-1.8-informational)](http://openjdk.java.net/)
@ -32,7 +32,7 @@ footer: MIT Licensed | Copyright © 2022.01.01 @Author Himit_ZH QQ Group:598587
[![Vue](https://img.shields.io/badge/Vue-2.6.11-success)](https://cn.vuejs.org/)
[![QQ Group 598587305](https://img.shields.io/badge/QQ%20Group-598587305-blue)](https://qm.qq.com/cgi-bin/qm/qr?k=WWGBZ5gfDiBZOcpNvM8xnZTfUq7BT4Rs&jump_from=webapi)
Hcode Online Judge (HOJ) : 基于前后端分离分布式架构的在线测评平台hoj前端使用vue后端主要使用springbootredismysqlnacos等技术**支持HDU、POJ、Codeforces包括GYM的vjudge判题同时适配手机端、电脑端浏览拥有讨论区与站内消息系统支持私有训练、公开训练题单还有完善的比赛功能打星队伍、关注队伍、外榜。**
Hcode Online Judge (HOJ) : 基于前后端分离分布式架构的在线测评平台hoj前端使用vue后端主要使用springbootredismysqlnacos等技术**支持HDU、POJ、Codeforces包括GYM的vjudge判题同时适配手机端、电脑端浏览拥有讨论区与站内消息系统支持私有训练、公开训练题单还有完善的判题模式(普通测评、特殊测评、交互测评)和完善的比赛功能(打星队伍、关注队伍、外榜)。**
[Github 仓库](https://github.com/HimitZH/HOJ)
[Gitee 仓库](https://gitee.com/himitzh0730/hoj)
@ -48,3 +48,10 @@ Hcode Online Judge (HOJ) : 基于前后端分离,分布式架构的在线测
QQ: [372347736](https://wpa.qq.com/msgrd?v=3&uin=372347736&site=qq&menu=yes)
HOJ交流群: [598587305](https://qm.qq.com/cgi-bin/qm/qr?k=WWGBZ5gfDiBZOcpNvM8xnZTfUq7BT4Rs&jump_from=webapi)
## 支持我们
**如果您觉得hoj好用想要支持一下开发者项目的开发与维护需要资金欢迎进行捐助。**
**可以微信扫下方二维码支持一波,不必在乎多少,尽自己能力支持即可。**
![wxpy](/docs/wxpay.png)

View File

@ -28,7 +28,8 @@ HOJ全称 Hcode Online Judge是基于springcloud+vue前后端分离
- 支持ACM、OI题目及比赛比赛拥有外榜、打星队伍、关注队伍等功能
- 拥有讨论区、题目讨论、比赛讨论、同时拥有站内消息系统
- 支持私有训练、公开训练(题单)
- 支持testlib的SPJ
- 支持testlib的特殊判题
- 支持交互判题
- 多样支持自身题目数据评测也支持其它知名OJHDU、Codeforces、POJ题目的爬取与提交
:::

View File

@ -44,55 +44,65 @@
```json
{
// 题目支持的语言如下,可多可少
"languages": ["C", "C++", "Java", "Python3", "Python2", "Golang", "C#"],
"samples": [
{
"input": "1.in",
"output": "1.out",
//"score": 10 // 如果是oi题目需要给测试点加得分
},
{
"input": "2.in",
"output": "2.out",
//"score": 10 // 如果是oi题目需要给测试点加得分
}
],
"tags": ["测试题","测试"], // 题目标签,一般不超过三个
"problem": {
"auth": 1, // 1 公开赛
"author": "admin", // 题目上传的作者,请使用用户名
"isRemote": false, // 均为非VJ题目不用修改
"problemId": "HOJ-1010", // 题目的展示id
"description": "", // 题目的描述支持markdown语法
"source": "", // 题目来源
"title": "", // 题目标题
"type": 0, // 0为ACM题目1为OI题目
"timeLimit": 1000, // 时间限制 单位是ms
"memoryLimit": 256, // 空间限制 单位是mb
"input": "", // 题目的输入描述
"output": "", // 题目的输出描述
"difficulty": 0, // 题目难度1为简单2为中等3为困难
"examples": "", // 题目的题面样例,格式为<input>输入</input><output>输出</output><input>输入</input><output>输出</output>
"ioScore": 100, // OI题目总得分与测试点总分一致
"codeShare": true, // 该题目是否允许用户共享其提交的代码
"hint": "", // 题目提示
"isRemoveEndBlank": true, // 评测数据的输出是否自动去掉行末空格
"openCaseResult": true, // 是否允许用户看到各个评测点的结果
// "spjLanguage:"C" // 特殊判题的程序代码语言
// "spjCode":"" // 特殊判题的代码
},
"isSpj": false, // 是否为特殊判题
"codeTemplates": [
{
"code": "", // 模板代码
"language": "C" // 模板代码语言
},
{
"code": "", // 模板代码
"language": "C++"// 模板代码语言
}
]
"judgeMode":"default", // 普通判题default, 特殊判题spj, 交互判题interactive
// 题目支持的语言如下,可多可少
"languages": ["C", "C++", "Java", "Python3", "Python2", "Golang", "C#"],
"samples": [
{
"input": "1.in",
"output": "1.out",
//"score": 10 // 如果是oi题目需要给测试点加得分
},
{
"input": "2.in",
"output": "2.out",
//"score": 10 // 如果是oi题目需要给测试点加得分
}
],
"tags": ["测试题","测试"], // 题目标签,一般不超过三个
"problem": {
"auth": 1, // 1 公开赛
"author": "admin", // 题目上传的作者,请使用用户名
"isRemote": false, // 均为非VJ题目不用修改
"problemId": "HOJ-1010", // 题目的展示id
"description": "", // 题目的描述支持markdown语法
"source": "", // 题目来源
"title": "", // 题目标题
"type": 0, // 0为ACM题目1为OI题目
"timeLimit": 1000, // 时间限制 单位是ms
"memoryLimit": 256, // 空间限制 单位是mb
"input": "", // 题目的输入描述
"output": "", // 题目的输出描述
"difficulty": 0, // 题目难度1为简单2为中等3为困难
"examples": "", // 题目的题面样例,格式为<input>输入</input><output>输出</output><input>输入</input><output>输出</output>
"ioScore": 100, // OI题目总得分与测试点总分一致
"codeShare": true, // 该题目是否允许用户共享其提交的代码
"hint": "", // 题目提示
"isRemoveEndBlank": true, // 评测数据的输出是否自动去掉行末空格
"openCaseResult": true, // 是否允许用户看到各个评测点的结果
// "spjLanguage:"C" // 特殊判题的程序代码语言
// "spjCode":"" // 特殊判题的代码
},
"codeTemplates": [
{
"code": "", // 模板代码
"language": "C" // 模板代码语言
},
{
"code": "", // 模板代码
"language": "C++"// 模板代码语言
}
],
// 用户程序的额外库文件 key文件名value文件内容如果没有请去掉
"userExtraFile":{
"testlib.h":"code",
"stdio.h":"..."
},
// 特殊或交互程序的额外库文件 key文件名value文件内容如果没有请去掉
"judgeExtraFile":{
"testlib.h":"code",
"stdio.h":"..."
}
}
```

289
docs/docs/use/judge-mode.md Normal file
View File

@ -0,0 +1,289 @@
# 判题模式
### 一、普通判题
**普通模式是程序在线评测平台(OJ)通用的判题模式**,主要的实现逻辑步骤如下:
:::tip
1. 选手程序读取题目标准输入文件的数据
2. 判题机执行代码逻辑得到选手输出
3. 再将选手输出与题目标准输出文件的数据进行对比,最终得到判题结果
:::
### 二、特殊判题
#### 1. 什么是特殊判题?
特殊判题Special Judge是指OJ将使用一个特定的程序来判断提交的程序的输出是不是正确的而不是单纯地看提交的程序的输出是否和标准输出一模一样。
#### 2. 使用场景
一般使用Special Judge都是因为题目的答案不唯一更具体一点说的话一般是两种情况
:::tip
- 题目最终要求输出一个解决方案,而且这个解决方案可能不唯一。
- 题目最终要求输出一个浮点数而且会告诉只要答案和标准答案相差不超过某个较小的数就可以比如0.01。这种情况保留3位小数、4位小数等等都是可以的而且多保留几位小数也没什么坏处。
:::
#### 3. 支持
HOJ支持testlib.h头文件的直接使用 具体使用文档请看[https://oi-wiki.org/tools/testlib/](https://oi-wiki.org/tools/testlib/)
#### 4. 例题
在创建题目的适合,选择开启特殊判题,编写特殊判题程序,然后编译通过便可。
> 后台对题目使用特殊判题时,请参考以下程序例子 判断精度
- 使用testlib.h来进行特殊判题
```cpp
#include <iostream>
#include "testlib.h"
using namespace std;
int main(int argc, char *args[]){
/**
inf: 输入文件流
ouf: 选手输出流
ans: 标准答案流
**/
registerTestlibCmd(argc, args);
double pans = ouf.readDouble();
double jans = ans.readDouble();
if (fabs(pans - jans)<0.01)
quitf(_ok, "The answer is correct.");
else
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
// quitf(_pe, "The answer is presentation error."); // 格式错误
// quitf(_fail, "The something wrong cause system error."); // 系统错误
}
```
- 读取文件进行特殊判题
```cpp
#include<iostream>
#include<cstdio>
#define AC 100
#define PE 101
#define WA 102
#define ERROR 103
using namespace std;
void close_file(FILE *f){
if(f != NULL){
fclose(f);
}
}
int main(int argc, char *args[]){
/**
args[1]:标准输入文件路径
args[2]:选手输出文件路径
args[3]:标准输出文件路径
**/
FILE *std_input_file = fopen(args[1], "r");
FILE *user_output_file = fopen(args[2], "r");
FILE *std_output_file = fopen(args[3], "r");
double std_out; // 标准输出
fscanf(user_output_file, "%lf", &std_out);
double user_output;// 用户输出
fscanf(std_output_file, "%lf", &user_output);
// 关闭文件流
close_file(std_input_file);
close_file(user_output_file);
close_file(std_output_file);
if (fabs(user_output - std_out)<=1e-6)
return AC;
else
return WA;
}
```
### 三、交互判题
**交互题** 是需要选手程序与测评程序交互来完成任务的题目。一类常见的情形是,选手程序向测评程序发出询问,并得到其反馈。
交互方式主要有如两种:**STDIO 交互**和**Grader 交互**
:::tip
主要的交互逻辑:交互程序的标准输出通过交互通道写到选手程序标准输入,选手程序的标准输出通过交互通道写到交互程序的标准输入,两者需要刷新输出缓存
:::
:::warning
在 C/C++ 中,`fflush(stdout)` 和 `std::cout << std::flush` 可以实现这个操作(使用 `std::cout << std::endl` 换行时也会自动刷新缓冲区,但是 `std::cout << '\n'` 不会)
:::
#### 1. 标准交互题
**A+B问题**
*选手程序*
```cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main(){
int a,b;
cin >> a >> b;
cout << a + b;
return 0;
}
```
*交互程序*这里使用testlib来实现但也可以自己读取文件实现
```cpp
#include "testlib.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
setName("Interactor A+B");
registerInteraction(argc, argv);
// 读取题目标准输入文件的数据
int a = inf.readInt();
int b = inf.readInt();
// 往交互通道写数据记得用endl刷新缓冲区
cout << a << " " << b << endl;
int ans;
// 读取用户程序写入到交互通道的数据
cin >> ans;
if (a + b == ans){ // 判断结果
quitf(_ok, "correct");
}else{
else quitf(_wa,"incorrect");
}
}
```
#### 2. 函数交互题
:::info
主要的交互逻辑:
1. 用户调用提供的库文件里面的方法执行答题逻辑,最后得出指定结果;
2. 交互测评程序根据选手调用交互库执行逻辑后得出的结果,来进行最终判断评测的结果。
:::
需要给选手的程序添加交互库,在后台的题目管理可以选择添加。
![函数交互题](https://img-blog.csdnimg.cn/e6f17df56f26488c895944713a80e9ed.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASGltaXRfWkg=,size_20,color_FFFFFF,t_70,g_se,x_16)
**交互库**:提供写好的方法给选手调用
```cpp
#include <bits/stdc++.h>
using namespace std;
namespace interactive {
static int n, m, cnt;
static bool hasUsedGetN = false;
static bool hasSubmitted = false;
void RE() {
puts("re");
exit(0);
}
int getn() {
if (hasUsedGetN) RE();
cin >> n;
hasUsedGetN = 1;
m = rand() % n + 1;
return n;
}
int query(int x) {
if (!hasUsedGetN || hasSubmitted) RE();
cnt++;
if (cnt > 100000) RE();
if (x == m)
return (rand() % 10 <= 3);
else
return (rand() % 10 <= 4);
}
void submit(int x) {
if (hasSubmitted) RE();
if (x == m)
puts("ok");
else
puts("wa");
hasSubmitted = 1;
}
}
using interactive::getn;
using interactive::query;
using interactive::submit;
```
**用户程序**:调用库文件的方法进行答题
```cpp
#include <bits/stdc++.h>
#include "interactive.h"
int main()
{
int n = getn();
for(int i = 1; i <= n; i++)
{
for(int j = 0; j < 900; j++)
{
if(query(i)) arr[i]++;
}
}
int min = 1000, ans = 0;
for(int i = 1; i <= n; i++)
{
if(arr[i] < min)
{
min = arr[i];
ans = i;
}
}
submit(ans);
return 0;
}
```
**交互测评程序**:根据选手调用交互库执行逻辑后得出的结果,来进行最终判断评测的结果
```cpp
#include "testlib.h"
#include <string>
int main(int argc, char* argv[]) {
registerTestlibCmd(argc, argv);
// 读取选手最终输出文件的数据来判断结果
std::string s = ouf.readToken();
if (s == "ok")
quitf(_ok, "Correct");
else
quitf(_wa, "Wrong Answer");
}
```

View File

@ -1,98 +0,0 @@
# 特殊判题
## 什么是特殊判题?
特殊判题Special Judge是指OJ将使用一个特定的程序来判断提交的程序的输出是不是正确的而不是单纯地看提交的程序的输出是否和标准输出一模一样。
## 使用场景
一般使用Special Judge都是因为题目的答案不唯一更具体一点说的话一般是两种情况
- 题目最终要求输出一个解决方案,而且这个解决方案可能不唯一。
- 题目最终要求输出一个浮点数而且会告诉只要答案和标准答案相差不超过某个较小的数就可以比如0.01。这种情况保留3位小数、4位小数等等都是可以的而且多保留几位小数也没什么坏处。
## 支持
HOJ支持testlib.h头文件的直接使用 具体使用文档请看[https://oi-wiki.org/tools/testlib/](https://oi-wiki.org/tools/testlib/)
## 例子:
在创建题目的适合,选择开启特殊判题,编写特殊判题程序,然后编译通过便可。
> 后台对题目使用特殊判题时,请参考以下程序例子 判断精度
- 使用Testlib特殊判题
```cpp
#include <iostream>
#include "testlib.h"
using namespace std;
int main(int argc, char *args[]){
/**
inf: 输入文件流
ouf: 选手输出流
ans: 标准答案流
**/
registerTestlibCmd(argc, args);
double pans = ouf.readDouble();
double jans = ans.readDouble();
if (fabs(pans - jans)<0.01)
quitf(_ok, "The answer is correct.");
else
quitf(_wa, "The answer is wrong: expected = %f, found = %f", jans, pans);
// quitf(_pe, "The answer is presentation error."); // 格式错误
// quitf(_fail, "The something wrong cause system error."); // 系统错误
}
```
- 读取文件进行特殊判题
```cpp
#include<iostream>
#include<cstdio>
#define AC 100
#define PE 101
#define WA 102
#define ERROR 103
using namespace std;
void close_file(FILE *f){
if(f != NULL){
fclose(f);
}
}
int main(int argc, char *args[]){
/**
args[1]:标准输入文件路径
args[2]:选手输出文件路径
args[3]:标准输出文件路径
**/
FILE *std_input_file = fopen(args[1], "r");
FILE *user_output_file = fopen(args[2], "r");
FILE *std_output_file = fopen(args[3], "r");
double std_out; // 标准输出
fscanf(user_output_file, "%lf", &std_out);
double user_output;// 用户输出
fscanf(std_output_file, "%lf", &user_output);
// 关闭文件流
close_file(std_input_file);
close_file(user_output_file);
close_file(std_output_file);
if (fabs(user_output - std_out)<=1e-6)
return AC;
else
return WA;
}
```

View File

@ -1,6 +1,8 @@
package top.hcode.hoj.controller.admin;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -19,7 +21,7 @@ import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.crawler.problem.ProblemStrategy;
import top.hcode.hoj.judge.Dispatcher;
import top.hcode.hoj.pojo.dto.ProblemDto;
import top.hcode.hoj.pojo.entity.judge.CompileSpj;
import top.hcode.hoj.pojo.entity.judge.CompileDTO;
import top.hcode.hoj.pojo.entity.problem.Problem;
import top.hcode.hoj.pojo.entity.problem.ProblemCase;
import top.hcode.hoj.pojo.vo.UserRolesVo;
@ -107,7 +109,7 @@ public class AdminProblemController {
@GetMapping("")
@RequiresAuthentication
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
public CommonResult getProblem(@Valid @RequestParam("pid") Long pid) {
public CommonResult getProblem(@RequestParam("pid") Long pid) {
Problem problem = problemService.getById(pid);
@ -204,15 +206,29 @@ public class AdminProblemController {
@PostMapping("/compile-spj")
@RequiresAuthentication
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {
if (StringUtils.isEmpty(compileSpj.getSpjSrc()) ||
StringUtils.isEmpty(compileSpj.getSpjLanguage())) {
if (StringUtils.isEmpty(compileDTO.getCode()) ||
StringUtils.isEmpty(compileDTO.getLanguage())) {
return CommonResult.errorResponse("参数不能为空!");
}
compileSpj.setToken(judgeToken);
return dispatcher.dispatcher("compile", "/compile-spj", compileSpj);
compileDTO.setToken(judgeToken);
return dispatcher.dispatcher("compile", "/compile-spj", compileDTO);
}
@PostMapping("/compile-interactive")
@RequiresAuthentication
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {
if (StringUtils.isEmpty(compileDTO.getCode()) ||
StringUtils.isEmpty(compileDTO.getLanguage())) {
return CommonResult.errorResponse("参数不能为空!");
}
compileDTO.setToken(judgeToken);
return dispatcher.dispatcher("compile", "/compile-interactive", compileDTO);
}
@GetMapping("/import-remote-oj-problem")

View File

@ -208,8 +208,19 @@ public class ImportAndExportProblemController {
problemCaseList.add(BeanUtil.mapToBean(tmp, ProblemCase.class, true));
}
// 格式化用户额外文件和判题额外文件
if (importProblemVo.getUserExtraFile() != null) {
JSONObject userExtraFileJson = JSONUtil.parseObj(importProblemVo.getUserExtraFile());
problem.setUserExtraFile(userExtraFileJson.toString());
}
if (importProblemVo.getJudgeExtraFile() != null) {
JSONObject judgeExtraFileJson = JSONUtil.parseObj(importProblemVo.getJudgeExtraFile());
problem.setJudgeExtraFile(judgeExtraFileJson.toString());
}
ProblemDto problemDto = new ProblemDto();
problemDto.setIsSpj(importProblemVo.getIsSpj())
problemDto.setJudgeMode(importProblemVo.getJudgeMode())
.setProblem(problem)
.setCodeTemplates(codeTemplates)
.setTags(tags)
@ -227,7 +238,6 @@ public class ImportAndExportProblemController {
}
/**
* @param pidList
* @param response

View File

@ -253,14 +253,17 @@ public class ImportFpsController {
problemSamples.add(new ProblemCase()
.setInput(infileName).setOutput(outfileName));
}
String mode = Constants.JudgeMode.DEFAULT.getMode();
if (problem.getSpjLanguage() != null) {
mode = Constants.JudgeMode.SPJ.getMode();
}
ProblemDto problemDto = new ProblemDto();
problemDto.setSamples(problemSamples)
.setIsUploadTestCase(true)
.setUploadTestcaseDir(problemTestCaseDir)
.setLanguages(languageList)
.setTags(null)
.setIsSpj(problem.getSpjLanguage() != null)
.setJudgeMode(mode)
.setProblem(problem)
.setCodeTemplates(codeTemplates);

View File

@ -190,7 +190,13 @@ public class ImportQDUOJController {
problem.setAuthor(userRolesVo.getUsername());
}
ProblemDto problemDto = new ProblemDto();
problemDto.setIsSpj(qdojProblemDto.getIsSpj())
String mode = Constants.JudgeMode.DEFAULT.getMode();
if (qdojProblemDto.getIsSpj()){
mode = Constants.JudgeMode.SPJ.getMode();
}
problemDto.setJudgeMode(mode)
.setProblem(problem)
.setCodeTemplates(qdojProblemDto.getCodeTemplates())
.setTags(tags)

View File

@ -16,7 +16,6 @@ import top.hcode.hoj.service.judge.impl.RemoteJudgeAccountServiceImpl;
import top.hcode.hoj.utils.Constants;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@ -56,8 +55,8 @@ public class Dispatcher {
toJudge(path, judgeData, judgeData.getJudge().getSubmitId(), judgeData.getRemoteJudgeProblem() != null);
break;
case "compile":
CompileSpj compileSpj = (CompileSpj) data;
return toCompile(path, compileSpj);
CompileDTO compileDTO = (CompileDTO) data;
return toCompile(path, compileDTO);
default:
throw new IllegalArgumentException("判题机不支持此调用类型");
}
@ -141,7 +140,7 @@ public class Dispatcher {
}
public CommonResult toCompile(String path, CompileSpj data) {
public CommonResult toCompile(String path, CompileDTO data) {
CommonResult result = CommonResult.errorResponse("没有可用的判题服务器,请重新尝试!");
JudgeServer judgeServer = chooseUtils.chooseServer(false);
if (judgeServer != null) {

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
import top.hcode.hoj.pojo.entity.problem.*;
import java.util.HashMap;
import java.util.List;
@ -24,9 +25,9 @@ public class ProblemDto {
private String uploadTestcaseDir;
private Boolean isSpj;
private String judgeMode;
private Boolean changeSpj;
private Boolean changeModeCode;
private List<Language> languages;

View File

@ -27,7 +27,11 @@ public class ImportProblemVo implements Serializable {
private List<HashMap<String,String>> codeTemplates;
private Boolean isSpj;
private HashMap<String,String> userExtraFile;
private HashMap<String,String> judgeExtraFile;
private String judgeMode;
public Map<String, Object> getProblem() {
return problem;
@ -69,11 +73,27 @@ public class ImportProblemVo implements Serializable {
this.codeTemplates = codeTemplates;
}
public Boolean getIsSpj() {
return isSpj;
public String getJudgeMode() {
return judgeMode;
}
public void setIsSpj(Boolean spj) {
isSpj = spj;
public void setJudgeMode(String judgeMode) {
this.judgeMode = judgeMode;
}
public HashMap<String, String> getUserExtraFile() {
return userExtraFile;
}
public void setUserExtraFile(HashMap<String, String> userExtraFile) {
this.userExtraFile = userExtraFile;
}
public HashMap<String, String> getJudgeExtraFile() {
return judgeExtraFile;
}
public void setJudgeExtraFile(HashMap<String, String> judgeExtraFile) {
this.judgeExtraFile = judgeExtraFile;
}
}

View File

@ -82,7 +82,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
public boolean adminUpdateProblem(ProblemDto problemDto) {
Problem problem = problemDto.getProblem();
if (!problemDto.getIsSpj()) {
if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
problem.setSpjLanguage(null).setSpjCode(null);
}
@ -306,19 +306,19 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
// 如果是选择上传测试文件的则需要遍历对应文件夹读取数据写入数据库,先前的题目数据一并清空
if (problemDto.getIsUploadTestCase()) {
// 获取代理bean对象执行异步方法===根据测试文件初始info
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(), caseVersion, pid, testcaseDir, problemDto.getSamples());
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(), caseVersion, pid, testcaseDir, problemDto.getSamples());
} else {
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
}
}
// 变化成spj或者取消 同时更新测试数据
else if (problemDto.getChangeSpj() != null && problemDto.getChangeSpj()) {
// 变化成spj或interactive或者取消 同时更新测试数据
else if (problemDto.getChangeModeCode() != null && problemDto.getChangeModeCode()) {
problem.setCaseVersion(caseVersion);
if (problemDto.getIsUploadTestCase()) {
// 获取代理bean对象执行异步方法===根据测试文件初始info
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(), caseVersion, pid, null, problemDto.getSamples());
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(), caseVersion, pid, null, problemDto.getSamples());
} else {
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
applicationContext.getBean(ProblemServiceImpl.class).initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
}
}
}
@ -341,8 +341,9 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
Problem problem = problemDto.getProblem();
if (!problemDto.getIsSpj()) {
problem.setSpjLanguage(null).setSpjCode(null);
if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
problem.setSpjLanguage(null)
.setSpjCode(null);
}
/**
@ -401,7 +402,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
}
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemCases);
// 获取代理bean对象执行异步方法===根据测试文件初始info
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getIsSpj(),
applicationContext.getBean(ProblemServiceImpl.class).initUploadTestCase(problemDto.getJudgeMode(),
problem.getCaseVersion(), pid, testcaseDir, problemDto.getSamples());
} else {
// oi题目需要求取平均值给每个测试点初始oi的score值默认总分100分
@ -414,7 +415,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid)); // 设置好新题目的pid
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
}
initHandTestCase(problemDto.getIsSpj(), problem.getCaseVersion(), pid, problemDto.getSamples());
initHandTestCase(problemDto.getJudgeMode(), problem.getCaseVersion(), pid, problemDto.getSamples());
}
// 为新的题目添加对应的tag可能tag是原表已有也可能是新的所以需要判断
@ -449,7 +450,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
// 初始化上传文件的测试数据写成json文件
@Async
public void initUploadTestCase(Boolean isSpj,
public void initUploadTestCase(String mode,
String version,
Long problemId,
String tmpTestcaseDir,
@ -464,7 +465,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
}
JSONObject result = new JSONObject();
result.set("isSpj", isSpj);
result.set("mode", mode);
result.set("version", version);
result.set("testCasesSize", problemCaseList.size());
@ -488,8 +489,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
FileReader outputFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(), CharsetUtil.UTF_8);
String output = outputFile.readString().replaceAll("\r\n", "\n");
// spj是根据特判程序输出判断结果所以无需初始化测试数据
if (!isSpj) {
// spj和interactive是根据特判程序输出判断结果所以无需初始化测试数据
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
// 原数据MD5
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(output.getBytes()));
// 原数据大小
@ -515,13 +516,13 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
// 初始化手动输入上传的测试数据写成json文件
@Async
public void initHandTestCase(Boolean isSpj,
public void initHandTestCase(String mode,
String version,
Long problemId,
List<ProblemCase> problemCaseList) {
JSONObject result = new JSONObject();
result.set("isSpj", isSpj);
result.set("mode", mode);
result.set("version", version);
result.set("testCasesSize", problemCaseList.size());
@ -548,8 +549,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
FileWriter outFile = new FileWriter(testCasesDir + "/" + outputName, CharsetUtil.UTF_8);
outFile.write(outputData);
// spj是根据特判程序输出判断结果所以无需初始化测试数据
if (!isSpj) {
// spj和interactive是根据特判程序输出判断结果所以无需初始化测试数据
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
// 原数据MD5
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(outputData.getBytes()));
// 原数据大小
@ -674,6 +675,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
}
@Override
@SuppressWarnings("All")
public ImportProblemVo buildExportProblem(Long pid, List<HashMap<String, Object>> problemCaseList,
HashMap<Long, String> languageMap, HashMap<Long, String> tagMap) {
// 导出相当于导入
@ -700,10 +702,19 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
codeTemplateList.add(tmp);
}
importProblemVo.setCodeTemplates(codeTemplateList);
importProblemVo.setIsSpj(problem.getSpjCode() != null);
importProblemVo.setJudgeMode(problem.getJudgeMode());
importProblemVo.setSamples(problemCaseList);
if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
HashMap<String,String> userExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
importProblemVo.setUserExtraFile(userExtraFileMap);
}
if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
HashMap<String,String> judgeExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
importProblemVo.setUserExtraFile(judgeExtraFileMap);
}
QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();
problemTagQueryWrapper.eq("pid", pid);
List<ProblemTag> problemTags = problemTagService.list(problemTagQueryWrapper);

View File

@ -278,4 +278,28 @@ public class Constants {
}
}
public enum JudgeMode {
DEFAULT("default"),
SPJ("spj"),
INTERACTIVE("interactive");
private final String mode;
JudgeMode(String mode) {
this.mode = mode;
}
public String getMode() {
return mode;
}
public static JudgeMode getJudgeMode(String mode){
for (JudgeMode judgeMode : JudgeMode.values()) {
if (judgeMode.getMode().equals(mode)) {
return judgeMode;
}
}
return null;
}
}
}

View File

@ -1,18 +1,14 @@
package top.hcode.hoj.controller;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import top.hcode.hoj.common.CommonResult;
import top.hcode.hoj.common.exception.CompileError;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.SandboxRun;
import top.hcode.hoj.pojo.entity.judge.CompileSpj;
import top.hcode.hoj.pojo.entity.judge.CompileDTO;
import top.hcode.hoj.pojo.entity.judge.Judge;
import top.hcode.hoj.pojo.entity.judge.ToJudge;
import top.hcode.hoj.pojo.entity.problem.Problem;
@ -22,8 +18,6 @@ import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.util.Constants;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
/**
@ -109,17 +103,30 @@ public class JudgeController {
@PostMapping(value = "/compile-spj")
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
public CommonResult compileSpj(@RequestBody CompileDTO compileDTO) {
if (!compileSpj.getToken().equals(judgeToken)) {
if (!compileDTO.getToken().equals(judgeToken)) {
return CommonResult.errorResponse("对不起!您使用的判题服务调用凭证不正确!访问受限!", CommonResult.STATUS_ACCESS_DENIED);
}
try {
judgeService.compileSpj(compileSpj.getSpjSrc(), compileSpj.getPid(), compileSpj.getSpjLanguage());
judgeService.compileSpj(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(), compileDTO.getExtraFiles());
return CommonResult.successResponse(null, "编译成功!");
} catch (SystemError systemError) {
return CommonResult.errorResponse(systemError.getStderr(), CommonResult.STATUS_ERROR);
}
}
@PostMapping(value = "/compile-interactive")
public CommonResult compileInteractive(@RequestBody CompileDTO compileDTO) {
if (!compileDTO.getToken().equals(judgeToken)) {
return CommonResult.errorResponse("对不起!您使用的判题服务调用凭证不正确!访问受限!", CommonResult.STATUS_ACCESS_DENIED);
}
try {
judgeService.compileInteractive(compileDTO.getCode(), compileDTO.getPid(), compileDTO.getLanguage(), compileDTO.getExtraFiles());
return CommonResult.successResponse(null, "编译成功!");
} catch (CompileError compileError) {
return CommonResult.errorResponse(compileError.getStderr(), CommonResult.STATUS_FAIL);
} catch (SystemError systemError) {
return CommonResult.errorResponse(systemError.getStderr(), CommonResult.STATUS_ERROR);
}

View File

@ -0,0 +1,129 @@
package top.hcode.hoj.judge;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import org.slf4j.LoggerFactory;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.entity.JudgeDTO;
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
import top.hcode.hoj.judge.entity.SandBoxRes;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.JudgeUtils;
import java.io.File;
import java.text.MessageFormat;
import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2022/1/2 20:46
* @Description:
*/
public abstract class AbstractJudge {
protected static final int SPJ_AC = 100;
protected static final int SPJ_PE = 101;
protected static final int SPJ_WA = 102;
protected static final int SPJ_ERROR = 103;
public JSONObject judge(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
JSONArray judgeResultList = judgeCase(judgeDTO, judgeGlobalDTO);
switch (judgeGlobalDTO.getJudgeMode()) {
case SPJ:
case DEFAULT:
return process(judgeDTO, judgeGlobalDTO, judgeResultList);
case INTERACTIVE:
return processMultiple(judgeDTO, judgeGlobalDTO, judgeResultList);
default:
throw new RuntimeException("The problem mode is error:" + judgeGlobalDTO.getJudgeMode());
}
}
public abstract JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError;
private JSONObject process(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList) throws SystemError {
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
SandBoxRes sandBoxRes = SandBoxRes.builder()
.stdout(((JSONObject) judgeResult.get("files")).getStr("stdout"))
.stderr(((JSONObject) judgeResult.get("files")).getStr("stderr"))
.time(judgeResult.getLong("time") / 1000000) // ns->ms
.memory(judgeResult.getLong("memory") / 1024) // b-->kb
.exitCode(judgeResult.getInt("exitStatus"))
.status(judgeResult.getInt("status"))
.build();
return checkResult(sandBoxRes, judgeDTO, judgeGlobalDTO);
}
private JSONObject processMultiple(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO, JSONArray judgeResultList) throws SystemError {
JSONObject userJudgeResult = (JSONObject) judgeResultList.get(0);
SandBoxRes userSandBoxRes = SandBoxRes.builder()
.stdout(((JSONObject) userJudgeResult.get("files")).getStr("stdout"))
.stderr(((JSONObject) userJudgeResult.get("files")).getStr("stderr"))
.time(userJudgeResult.getLong("time") / 1000000) // ns->ms
.memory(userJudgeResult.getLong("memory") / 1024) // b-->kb
.exitCode(userJudgeResult.getInt("exitStatus"))
.status(userJudgeResult.getInt("status"))
.build();
JSONObject interactiveJudgeResult = (JSONObject) judgeResultList.get(1);
SandBoxRes interactiveSandBoxRes = SandBoxRes.builder()
.stdout(((JSONObject) interactiveJudgeResult.get("files")).getStr("stdout"))
.stderr(((JSONObject) interactiveJudgeResult.get("files")).getStr("stderr"))
.time(interactiveJudgeResult.getLong("time") / 1000000) // ns->ms
.memory(interactiveJudgeResult.getLong("memory") / 1024) // b-->kb
.exitCode(interactiveJudgeResult.getInt("exitStatus"))
.status(interactiveJudgeResult.getInt("status"))
.build();
return checkMultipleResult(userSandBoxRes, interactiveSandBoxRes, judgeDTO, judgeGlobalDTO);
}
public abstract JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError;
public abstract JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO);
protected List<String> parseRunCommand(String command,
Constants.RunConfig runConfig,
String testCaseInputName,
String userOutputName,
String testCaseOutputName) {
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),
runConfig.getExeName(), Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseInputName,
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + userOutputName,
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseOutputName);
return JudgeUtils.translateCommandline(command);
}
protected JSONObject parseTestLibErr(String err) {
JSONObject res = new JSONObject(2);
if (err.startsWith("ok ")) {
res.set("code", SPJ_AC);
res.set("errMsg", err.split("ok ")[1]);
} else if (err.startsWith("wrong answer ")) {
res.set("code", SPJ_WA);
res.set("errMsg", err.split("wrong answer ")[1]);
} else if (err.startsWith("wrong output format ")) {
res.set("code", SPJ_WA);
res.set("errMsg", "May be output presentation error. " + err.split("wrong output format")[1]);
} else if (err.startsWith("FAIL ")) {
res.set("code", SPJ_ERROR);
res.set("errMsg", err.split("FAIL ")[1]);
} else {
res.set("code", SPJ_ERROR);
res.set("errMsg", err);
}
return res;
}
}

View File

@ -9,8 +9,10 @@ import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.JudgeUtils;
import java.io.File;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
@ -19,7 +21,8 @@ import java.util.List;
* @Description: 判题流程解耦重构2.0该类只负责编译
*/
public class Compiler {
public static String compile(Constants.CompileConfig compileConfig, String code, String language) throws SystemError, CompileError, SubmitError {
public static String compile(Constants.CompileConfig compileConfig, String code,
String language, HashMap<String, String> extraFiles) throws SystemError, CompileError, SubmitError {
if (compileConfig == null) {
throw new RuntimeException("Unsupported language " + language);
@ -29,12 +32,13 @@ public class Compiler {
JSONArray result = SandboxRun.compile(compileConfig.getMaxCpuTime(),
compileConfig.getMaxRealTime(),
compileConfig.getMaxMemory(),
128 * 1024 * 1024L,
256 * 1024 * 1024L,
compileConfig.getSrcName(),
compileConfig.getExeName(),
parseCompileCommand(compileConfig.getCommand(), compileConfig),
compileConfig.getEnvs(),
code,
extraFiles,
true,
false,
null
@ -47,17 +51,17 @@ public class Compiler {
String fileId = ((JSONObject) compileResult.get("fileIds")).getStr(compileConfig.getExeName());
if (StringUtils.isEmpty(fileId)) {
throw new SubmitError("Executable file not found.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
throw new SubmitError("Executable file not found.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
((JSONObject) compileResult.get("files")).getStr("stderr"));
}
return fileId;
}
public static Boolean compileSpj(String code, Long pid, String spjLanguage) throws SystemError {
public static Boolean compileSpj(String code, Long pid, String language, HashMap<String, String> extraFiles) throws SystemError {
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + spjLanguage);
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + language);
if (spjCompiler == null) {
throw new RuntimeException("Unsupported language " + spjLanguage);
throw new RuntimeException("Unsupported SPJ language:" + language);
}
boolean copyOutExe = true;
@ -69,15 +73,16 @@ public class Compiler {
JSONArray res = SandboxRun.compile(spjCompiler.getMaxCpuTime(),
spjCompiler.getMaxRealTime(),
spjCompiler.getMaxMemory(),
128 * 1024 * 1024L,
256 * 1024 * 1024L,
spjCompiler.getSrcName(),
spjCompiler.getExeName(),
parseCompileCommand(spjCompiler.getCommand(), spjCompiler),
spjCompiler.getEnvs(),
code,
extraFiles,
false,
copyOutExe,
Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + "/" + pid
Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + pid
);
JSONObject compileResult = (JSONObject) res.get(0);
if (compileResult.getInt("status").intValue() != Constants.Judge.STATUS_ACCEPTED.getStatus()) {
@ -87,6 +92,42 @@ public class Compiler {
return true;
}
public static Boolean compileInteractive(String code, Long pid, String language, HashMap<String, String> extraFiles) throws SystemError {
Constants.CompileConfig interactiveCompiler = Constants.CompileConfig.getCompilerByLanguage("INTERACTIVE-" + language);
if (interactiveCompiler == null) {
throw new RuntimeException("Unsupported interactive language:" + language);
}
boolean copyOutExe = true;
if (pid == null) { // 题目id为空则不进行本地存储可能为新建题目时测试特判程序是否正常的判断而已
copyOutExe = false;
}
// 调用安全沙箱对特别判题程序进行编译
JSONArray res = SandboxRun.compile(interactiveCompiler.getMaxCpuTime(),
interactiveCompiler.getMaxRealTime(),
interactiveCompiler.getMaxMemory(),
256 * 1024 * 1024L,
interactiveCompiler.getSrcName(),
interactiveCompiler.getExeName(),
parseCompileCommand(interactiveCompiler.getCommand(), interactiveCompiler),
interactiveCompiler.getEnvs(),
code,
extraFiles,
false,
copyOutExe,
Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent() + File.separator + pid
);
JSONObject compileResult = (JSONObject) res.get(0);
if (compileResult.getInt("status").intValue() != Constants.Judge.STATUS_ACCEPTED.getStatus()) {
throw new SystemError("Interactive Judge Code Compile Error.", ((JSONObject) compileResult.get("files")).getStr("stdout"),
((JSONObject) compileResult.get("files")).getStr("stderr"));
}
return true;
}
private static List<String> parseCompileCommand(String command, Constants.CompileConfig compileConfig) {
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),

View File

@ -1,95 +1,103 @@
package top.hcode.hoj.judge;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Component;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.entity.JudgeDTO;
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
import top.hcode.hoj.judge.task.DefaultJudge;
import top.hcode.hoj.judge.task.InteractiveJudge;
import top.hcode.hoj.judge.task.SpecialJudge;
import top.hcode.hoj.pojo.entity.problem.Problem;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.JudgeUtils;
import top.hcode.hoj.util.ThreadPoolUtils;
import javax.annotation.Resource;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.*;
/**
* @Author: Himit_ZH
* @Date: 2021/4/16 12:15
* @Description: 判题流程解耦重构2.0该类负责输入数据进入程序进行测评
* @Description: 判题流程解耦重构3.0该类负责输入数据进入程序进行测评
*/
@Slf4j(topic = "hoj")
@Component
public class JudgeRun {
@Resource
private DefaultJudge defaultJudge;
private static final int SPJ_AC = 100;
@Resource
private SpecialJudge specialJudge;
private static final int SPJ_PE = 101;
@Resource
private InteractiveJudge interactiveJudge;
private static final int SPJ_WA = 102;
private static final int SPJ_ERROR = 103;
private Long submitId;
private Long problemId;
private String testCasesDir;
private JSONObject testCasesInfo;
private Constants.RunConfig runConfig;
private Constants.RunConfig spjRunConfig;
public JudgeRun(Long submitId, Long problemId, String testCasesDir, JSONObject testCasesInfo, Constants.RunConfig runConfig, Constants.RunConfig spjRunConfig) {
this.submitId = submitId;
this.problemId = problemId;
this.testCasesDir = testCasesDir;
this.testCasesInfo = testCasesInfo;
this.runConfig = runConfig;
this.spjRunConfig = spjRunConfig;
}
public List<JSONObject> judgeAllCase(String userFileId,
Long maxTime,
Long maxMemory,
Integer maxStack,
Boolean getUserOutput,
Boolean isRemoveEOFBlank,
String spjExeName)
public List<JSONObject> judgeAllCase(Long submitId,
Problem problem,
String judgeLanguage,
String testCasesDir,
JSONObject testCasesInfo,
String userFileId,
Boolean getUserOutput)
throws SystemError, ExecutionException, InterruptedException {
if (testCasesInfo == null) {
throw new SystemError("The evaluation data of the problem does not exist", null, null);
}
List<FutureTask<JSONObject>> futureTasks = new ArrayList<>();
JSONArray testcaseList = (JSONArray) testCasesInfo.get("testCases");
Boolean isSpj = testCasesInfo.getBool("isSpj");
// 默认给题目限制时间+200ms用来测评
final Long testTime = maxTime + 200;
Long testTime = (long) problem.getTimeLimit() + 200;
Constants.JudgeMode judgeMode = Constants.JudgeMode.getJudgeMode(problem.getJudgeMode());
if (judgeMode == null) {
throw new RuntimeException("The judge mode of problem " + problem.getProblemId() + " error:" + problem.getJudgeMode());
}
// 用户输出的文件夹
String runDir = Constants.JudgeDir.RUN_WORKPLACE_DIR.getContent() + File.separator + submitId;
Constants.RunConfig runConfig = Constants.RunConfig.getRunnerByLanguage(judgeLanguage);
Constants.RunConfig spjConfig = Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage());
Constants.RunConfig interactiveConfig = Constants.RunConfig.getRunnerByLanguage("INTERACTIVE-" + problem.getSpjLanguage());
JudgeGlobalDTO judgeGlobalDTO = JudgeGlobalDTO.builder()
.problemId(problem.getId())
.judgeMode(judgeMode)
.userFileId(userFileId)
.runDir(runDir)
.testTime(testTime)
.maxMemory(problem.getMemoryLimit() * 1024L)
.maxTime((long) problem.getTimeLimit())
.maxStack(problem.getStackLimit())
.testCaseInfo(testCasesInfo)
.judgeExtraFiles(JudgeUtils.getProblemExtraFileMap(problem, "judge"))
.runConfig(runConfig)
.spjRunConfig(spjConfig)
.interactiveRunConfig(interactiveConfig)
.needUserOutputFile(getUserOutput)
.removeEOLBlank(problem.getIsRemoveEndBlank())
.build();
for (int index = 0; index < testcaseList.size(); index++) {
JSONObject testcase = (JSONObject) testcaseList.get(index);
// 将每个需要测试的线程任务加入任务列表中
final int testCaseId = index;
final int testCaseId = index + 1;
// 输入文件名
final String inputFileName = testcase.getStr("inputName");
// 输出文件名
final String outputFileName = testcase.getStr("outputName");
// 测试样例的路径
// 题目数据的输入文件的路径
final String testCaseInputPath = testCasesDir + File.separator + inputFileName;
// 题目数据的输出文件的路径
final String testCaseOutputPath = testCasesDir + File.separator + outputFileName;
// 数据库表的测试样例id
final Long caseId = testcase.getLong("caseId", null);
// 该测试点的满分
@ -97,52 +105,34 @@ public class JudgeRun {
final Long maxOutputSize = Math.max(testcase.getLong("outputSize", 0L) * 2, 16 * 1024 * 1024L);
if (!isSpj) {
futureTasks.add(new FutureTask<>(new Callable<JSONObject>() {
@Override
public JSONObject call() throws SystemError {
JSONObject result = judgeOneCase(userFileId,
testCaseId,
runDir,
testCaseInputPath,
testTime,// 默认给题目限制时间+200ms用来测评
maxTime,
maxMemory,
maxStack,
maxOutputSize,
getUserOutput,
isRemoveEOFBlank);
result.set("caseId", caseId);
result.set("score", score);
result.set("inputFileName", inputFileName);
result.set("outputFileName", outputFileName);
return result;
}
}));
} else {
final String testCaseOutputPath = testCasesDir + File.separator + outputFileName;
futureTasks.add(new FutureTask<>(new Callable<JSONObject>() {
@Override
public JSONObject call() throws SystemError {
JSONObject result = spjJudgeOneCase(userFileId,
testCaseId,
runDir,
testCaseInputPath,
testCaseOutputPath,
testTime,// 默认给题目限制时间+200ms用来测评
maxTime,
maxMemory,
maxOutputSize,
maxStack,
spjExeName);
result.set("caseId", caseId);
result.set("score", score);
result.set("inputFileName", inputFileName);
result.set("outputFileName", outputFileName);
return result;
}
}));
}
JudgeDTO judgeDTO = JudgeDTO.builder()
.testCaseId(testCaseId)
.testCaseInputPath(testCaseInputPath)
.testCaseOutputPath(testCaseOutputPath)
.maxOutputSize(maxOutputSize)
.build();
futureTasks.add(new FutureTask<>(() -> {
JSONObject result;
switch (judgeMode) {
case DEFAULT:
result = defaultJudge.judge(judgeDTO, judgeGlobalDTO);
break;
case SPJ:
result = specialJudge.judge(judgeDTO, judgeGlobalDTO);
break;
case INTERACTIVE:
result = interactiveJudge.judge(judgeDTO, judgeGlobalDTO);
break;
default:
throw new RuntimeException("The problem mode is error:" + judgeMode);
}
result.set("caseId", caseId);
result.set("score", score);
result.set("inputFileName", inputFileName);
result.set("outputFileName", outputFileName);
return result;
}));
}
// 提交到线程池进行执行
@ -169,464 +159,4 @@ public class JudgeRun {
return result;
}
private JSONObject spjJudgeOneCase(String userFileId,
Integer testCaseId,
String runDir,
String testCaseInputFilePath,
String testCaseOutputFilePath,
Long testTime,
Long maxTime,
Long maxMemory,
Long maxOutputSize,
Integer maxStack,
String spjExeName) throws SystemError {
// 调用安全沙箱使用测试点对程序进行测试
JSONArray judgeResultList = SandboxRun.testCase(
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
testCaseInputFilePath,
testTime,
maxOutputSize,
maxStack,
runConfig.getExeName(),
userFileId);
JSONObject result = new JSONObject();
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
// 获取跑题用户输出或错误输出
String userStdOut = ((JSONObject) judgeResult.get("files")).getStr("stdout");
String userErrOut = ((JSONObject) judgeResult.get("files")).getStr("stderr");
StringBuilder errMsg = new StringBuilder();
// 获取程序运行内存 b-->kb
long memory = judgeResult.getLong("memory") / 1024;
// 获取程序运行时间 ns->ms
long time = judgeResult.getLong("time") / 1000000;
// 异常退出的状态码
int exitCode = judgeResult.getInt("exitStatus");
// 如果测试跑题无异常
if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
// 对结果的时间损耗和空间损耗与题目限制做比较判断是否mle和tle
if (time >= maxTime) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (memory >= maxMemory) {
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else {
// 对于当前测试样例用户程序的输出对应生成的文件
String userOutputFilePath = runDir + File.separator + (testCaseId + 1) + ".out";
FileWriter stdWriter = new FileWriter(userOutputFilePath);
stdWriter.write(userStdOut);
// 特判程序的路径
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + problemId + File.separator + spjExeName;
String userOutputFileName = problemId + "_user_output";
String testCaseInputFileName = problemId + "_input";
String testCaseOutputFileName = problemId + "_output";
// 进行spj程序运行比对
JSONObject spjResult = spjCheckResult(userOutputFilePath,
userOutputFileName,
testCaseInputFilePath,
testCaseInputFileName,
testCaseOutputFilePath,
testCaseOutputFileName,
spjExeSrc,
spjExeName,
runDir);
int code = spjResult.getInt("code");
if (code == SPJ_WA) {
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
} else if (code == SPJ_AC) {
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
} else if (code == SPJ_PE) {
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
} else {
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
}
String spjErrMsg = spjResult.getStr("errMsg");
if (!StringUtils.isEmpty(spjErrMsg)) {
errMsg.append(spjErrMsg).append(" ");
}
}
} else if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus()) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (exitCode != 0) {
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
if (exitCode < 32) {
errMsg.append(String.format("ExitCode: %s (%s)\n", exitCode, SandboxRun.signals.get(exitCode)));
} else {
errMsg.append(String.format("ExitCode: %s\n", exitCode));
}
} else {
result.set("status", judgeResult.getInt("status"));
}
// b
result.set("memory", memory);
// ns->ms
result.set("time", time);
// 记录该测试点的错误信息
if (!StringUtils.isEmpty(errMsg.toString())) {
result.set("errMsg", errMsg.toString());
}
if (!StringUtils.isEmpty(userErrOut)) {
// 同时记录错误信息
errMsg.append(userErrOut);
// 对于当前测试样例用户的错误提示生成对应文件
FileWriter errWriter = new FileWriter(runDir + File.separator + testCaseId + ".err");
errWriter.write(userErrOut);
}
return result;
}
private JSONObject spjCheckResult(String userOutputFilePath,
String userOutputFileName,
String testCaseInputFilePath,
String testCaseInputFileName,
String testCaseOutputFilePath,
String testCaseOutputFileName,
String spjExeSrc,
String spjExeName,
String runDir) throws SystemError {
// 调用安全沙箱运行spj程序
JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
spjRunConfig.getEnvs(),
userOutputFilePath,
userOutputFileName,
testCaseInputFilePath,
testCaseInputFileName,
testCaseOutputFilePath,
testCaseOutputFileName,
spjExeSrc,
spjExeName);
JSONObject result = new JSONObject();
JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);
// 获取跑题用户输出或错误输出
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
if (!StringUtils.isEmpty(spjErrOut)) {
result.set("errMsg", spjErrOut);
}
// 退出状态码
int exitCode = spjJudgeResult.getInt("exitStatus");
// 如果测试跑题无异常
if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
result.set("code", SPJ_AC);
} else {
result.set("code", exitCode);
}
} else if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_RUNTIME_ERROR.getStatus()) {
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
result.set("code", exitCode);
} else {
if (!StringUtils.isEmpty(spjErrOut)) {
// 适配testlib.h 根据错误信息前缀判断
return parseTestlibErr(spjErrOut);
} else {
result.set("code", SPJ_ERROR);
}
}
} else {
result.set("code", SPJ_ERROR);
}
return result;
}
private JSONObject parseTestlibErr(String err) {
JSONObject res = new JSONObject(2);
if (err.startsWith("ok ")) {
res.set("code", SPJ_AC);
res.set("errMsg", err.split("ok ")[1]);
} else if (err.startsWith("wrong answer ")) {
res.set("code", SPJ_WA);
res.set("errMsg", err.split("wrong answer ")[1]);
} else if (err.startsWith("wrong output format ")) {
res.set("code", SPJ_WA);
res.set("errMsg", "May be output presentation error. " + err.split("wrong output format")[1]);
} else if (err.startsWith("FAIL ")) {
res.set("code", SPJ_ERROR);
res.set("errMsg", err.split("FAIL ")[1]);
} else {
res.set("code", SPJ_ERROR);
res.set("errMsg", err);
}
return res;
}
private JSONObject judgeOneCase(String userFileId,
Integer testCaseId,
String runDir,
String testCasePath,
Long testTime,
Long maxTime,
Long maxMemory,
Integer maxStack,
Long maxOutputSize,
Boolean getUserOutput,
Boolean isRemoveEOFBlank) throws SystemError {
// 调用安全沙箱使用测试点对程序进行测试
JSONArray judgeResultList = SandboxRun.testCase(parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
testCasePath,
testTime,
maxOutputSize,
maxStack,
runConfig.getExeName(),
userFileId);
JSONObject result = new JSONObject();
JSONObject judgeResult = (JSONObject) judgeResultList.get(0);
// 获取跑题用户输出或错误输出
String userStdOut = ((JSONObject) judgeResult.get("files")).getStr("stdout");
String userErrOut = ((JSONObject) judgeResult.get("files")).getStr("stderr");
StringBuffer errMsg = new StringBuffer();
// 获取程序运行内存 b-->kb
long memory = judgeResult.getLong("memory") / 1024;
// 获取程序运行时间 ns->ms
long time = judgeResult.getLong("time") / 1000000;
// 异常退出的状态码
int exitCode = judgeResult.getInt("exitStatus");
// 如果测试跑题无异常
if (judgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
// 对结果的时间损耗和空间损耗与题目限制做比较判断是否mle和tle
if (time >= maxTime) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (memory >= maxMemory) {
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else {
// 与原测试数据输出的md5进行对比 AC或者是WA
result.set("status", compareOutput(testCaseId, userStdOut, isRemoveEOFBlank));
}
} else if (judgeResult.getInt("status").equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (exitCode != 0) {
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
if (exitCode < 32) {
errMsg.append(String.format("ExitCode: %s (%s)\n", exitCode, SandboxRun.signals.get(exitCode)));
} else {
errMsg.append(String.format("ExitCode: %s\n", exitCode));
}
} else {
result.set("status", judgeResult.getInt("status"));
}
// b
result.set("memory", memory);
// ns->ms
result.set("time", time);
if (!StringUtils.isEmpty(userStdOut)) {
// 对于当前测试样例用户程序的输出对应生成的文件
FileWriter stdWriter = new FileWriter(runDir + "/" + testCaseId + ".out");
stdWriter.write(userStdOut);
}
if (!StringUtils.isEmpty(userErrOut)) {
// 对于当前测试样例用户的错误提示生成对应文件
FileWriter errWriter = new FileWriter(runDir + "/" + testCaseId + ".err");
errWriter.write(userErrOut);
// 同时记录错误信息
errMsg.append(userErrOut);
}
// 记录该测试点的错误信息
if (!StringUtils.isEmpty(errMsg.toString())) {
result.set("errMsg", errMsg.toString());
}
if (getUserOutput) { // 如果需要获取用户对于该题目的输出
result.set("output", userStdOut);
}
return result;
}
private static List<String> parseRunCommand(String command,
Constants.RunConfig runConfig,
String testCaseInputName,
String userOutputName,
String testCaseOutputName) {
command = MessageFormat.format(command, Constants.JudgeDir.TMPFS_DIR.getContent(),
runConfig.getExeName(), Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseInputName,
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + userOutputName,
Constants.JudgeDir.TMPFS_DIR.getContent() + File.separator + testCaseOutputName);
return JudgeUtils.translateCommandline(command);
}
public JSONObject getTestCasesInfo(int testCaseId) {
return (JSONObject) ((JSONArray) testCasesInfo.get("testCases")).get(testCaseId);
}
// 根据评测结果与用户程序输出的字符串MD5进行对比
private Integer compareOutput(int testCaseId, String userOutput, Boolean isRemoveEOFBlank) {
// 如果当前题目选择默认去掉字符串末位空格
if (isRemoveEOFBlank) {
String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("EOFStrippedOutputMd5"))) {
return Constants.Judge.STATUS_ACCEPTED.getStatus();
}
} else { // 不选择默认去掉文末空格 与原数据进行对比
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("outputMd5"))) {
return Constants.Judge.STATUS_ACCEPTED.getStatus();
}
}
// 如果不AC,进行PE判断否则为WA
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll("\\s+", "").getBytes());
if (userOutputMd5.equals(getTestCasesInfo(testCaseId).getStr("allStrippedOutputMd5"))) {
return Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus();
} else {
return Constants.Judge.STATUS_WRONG_ANSWER.getStatus();
}
}
// 去除行末尾空白符
public static String rtrim(String value) {
if (value == null) return null;
StringBuilder sb = new StringBuilder();
String[] strArr = value.split("\n");
for (String str : strArr) {
sb.append(str.replaceAll("\\s+$", "")).append("\n");
}
return sb.toString().replaceAll("\\s+$", "");
}
/*
交互题 暂时不启用
*/
private JSONObject interactOneCase(String userFileId,
Integer testCaseId,
String runDir,
String testCaseInputFilePath,
String testCaseOutputFilePath,
Long maxTime,
Long maxMemory,
Integer maxStack,
String userExeName,
String spjExeName,
Boolean getUserOutput) throws SystemError {
// 对于当前测试样例用户程序的输出对应生成的文件正常就输出数据错误就是输出错误信息
String realUserOutputFile = runDir + File.separator + testCaseId + ".out";
// 特判程序的路径
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator + problemId + File.separator + spjExeName;
String testCaseInputFileName = problemId + "_input";
String testCaseOutputFileName = problemId + "_output";
JSONArray judgeResultList = SandboxRun.interactTestCase(
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
userExeName,
userFileId,
testCaseInputFilePath,
testCaseInputFileName,
maxTime,
maxStack,
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, null, testCaseOutputFileName),
spjRunConfig.getEnvs(),
spjExeSrc,
testCaseOutputFilePath,
testCaseOutputFileName,
spjExeName);
JSONObject result = new JSONObject();
// 用户程序输出写入文件
FileWriter fileWriter = new FileWriter(realUserOutputFile);
JSONObject userJudgeResult = (JSONObject) judgeResultList.get(0);
JSONObject spjJudgeResult = (JSONObject) judgeResultList.get(1);
// 特判程序输出或错误输出
String spjStdOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stdout");
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
// 获取用户程序运行内存 b-->kb
long memory = userJudgeResult.getLong("memory") / 1024;
// 获取用户程序运行时间 ns->ms
long time = userJudgeResult.getLong("time") / 1000000;
// 用户程序的退出状态码
int userExitCode = userJudgeResult.getInt("exitStatus");
// 记录错误信息
StringBuffer errMsg = new StringBuffer();
// 如果用户提交的代码运行无误
if (userJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
// 如果运行超过题目限制时间直接TLE
if (time >= maxTime) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (memory >= maxMemory) { // 如果运行超过题目限制空间直接MLE
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else { // 校验特判程序的输出
// 根据特判程序的退出状态码进行判断
if (spjJudgeResult.getInt("exitStatus") == SPJ_AC) {
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
} else if (spjJudgeResult.getInt("exitStatus") == SPJ_WA) {
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
} else {
throw new SystemError(spjErrOut, spjStdOut, spjErrOut);
}
}
} else if (userJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus()) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (userExitCode != 0) {
if (userExitCode < 32) {
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
errMsg.append(String.format("ExitCode: %s (%s)\n", userExitCode, SandboxRun.signals.get(userExitCode)));
} else {
errMsg.append(String.format("ExitCode: %s\n", userExitCode));
}
String err = ((JSONObject) userJudgeResult.get("files")).getStr("stderr", null);
errMsg.append(err);
result.set("errMsg", errMsg.toString());
fileWriter.write(err);
} else {
result.set("status", userJudgeResult.getInt("status"));
}
// kb
result.set("memory", memory);
// ms
result.set("time", time);
if (getUserOutput) { // 如果需要获取用户对于该题目的输出,只提供特判程序输出
result.set("output", spjStdOut);
}
return result;
}
}

View File

@ -3,6 +3,7 @@ package top.hcode.hoj.judge;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -16,7 +17,9 @@ import top.hcode.hoj.pojo.entity.problem.Problem;
import top.hcode.hoj.service.impl.JudgeCaseServiceImpl;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.JudgeUtils;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;
@ -24,15 +27,17 @@ import java.util.*;
@Component
public class JudgeStrategy {
@Autowired
@Resource
private JudgeServiceImpl judgeService;
@Autowired
@Resource
private ProblemTestCaseUtils problemTestCaseUtils;
@Autowired
@Resource
private JudgeCaseServiceImpl judgeCaseService;
@Resource
private JudgeRun judgeRun;
public HashMap<String, Object> judge(Problem problem, Judge judge) {
@ -41,19 +46,21 @@ public class JudgeStrategy {
String userFileId = null;
try {
// 对用户源代码进行编译 获取tmpfs中的fileId
userFileId = Compiler.compile(Constants.CompileConfig.getCompilerByLanguage(judge.getLanguage()), judge.getCode(), judge.getLanguage());
userFileId = Compiler.compile(Constants.CompileConfig.getCompilerByLanguage(judge.getLanguage()),
judge.getCode(), judge.getLanguage(), JudgeUtils.getProblemExtraFileMap(problem, "user"));
// 测试数据文件所在文件夹
String testCasesDir = Constants.JudgeDir.TEST_CASE_DIR.getContent() + File.separator + "problem_" + problem.getId();
// 从文件中加载测试数据json
JSONObject testCasesInfo = problemTestCaseUtils.loadTestCaseInfo(problem.getId(), testCasesDir, problem.getCaseVersion(), !StringUtils.isEmpty(problem.getSpjCode()));
JSONObject testCasesInfo = problemTestCaseUtils.loadTestCaseInfo(problem.getId(), testCasesDir, problem.getCaseVersion(),
problem.getJudgeMode());
JSONArray testcaseList = (JSONArray) testCasesInfo.get("testCases");
String version = testCasesInfo.getStr("version");
// 检查是否为spj同时是否有spj编译完成的文件若不存在就先编译生成该spj文件同时也要检查版本
Boolean hasSpjOrNotSpj = checkOrCompileSpj(problem, version);
// 如果该题为spj但是没有spj程序
if (!hasSpjOrNotSpj) {
// 检查是否为spj或者interactive同时是否有对应编译完成的文件若不存在就先编译生成该文件同时也要检查版本
Boolean isOk = checkOrCompileExtraProgram(problem, version);
if (!isOk) {
result.put("code", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
result.put("errMsg", "The special judge code does not exist.");
result.put("errMsg", "The special judge or interactive program code does not exist.");
result.put("time", 0);
result.put("memory", 0);
return result;
@ -62,28 +69,15 @@ public class JudgeStrategy {
// 更新状态为评测数据中
judge.setStatus(Constants.Judge.STATUS_JUDGING.getStatus());
judgeService.saveOrUpdate(judge);
// spj程序的名字
String spjExeName = null;
if (!StringUtils.isEmpty(problem.getSpjCode())) {
spjExeName = Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage()).getExeName();
}
JudgeRun judgeRun = new JudgeRun(
judge.getSubmitId(),
problem.getId(),
// 开始测试每个测试点
List<JSONObject> allCaseResultList = judgeRun.judgeAllCase(judge.getSubmitId(),
problem,
judge.getLanguage(),
testCasesDir,
testCasesInfo,
Constants.RunConfig.getRunnerByLanguage(judge.getLanguage()),
Constants.RunConfig.getRunnerByLanguage("SPJ-" + problem.getSpjLanguage())
);
// 开始测试每个测试点
List<JSONObject> allCaseResultList = judgeRun.judgeAllCase(
userFileId,
problem.getTimeLimit() * 1L,
problem.getMemoryLimit() * 1024L,
problem.getStackLimit(),
false,
problem.getIsRemoveEndBlank(),
spjExeName);
false);
// 对全部测试点结果进行评判,获取最终评判结果
HashMap<String, Object> judgeInfo = getJudgeInfo(allCaseResultList, problem, judge);
return judgeInfo;
@ -121,18 +115,43 @@ public class JudgeStrategy {
return result;
}
private Boolean checkOrCompileExtraProgram(Problem problem, String version) throws CompileError, SystemError {
public Boolean checkOrCompileSpj(Problem problem, String version) throws CompileError, SystemError {
// 如果是需要特判的题目则需要检测特批程序是否已经编译否则进行编译
if (!StringUtils.isEmpty(problem.getSpjCode())) {
Constants.CompileConfig spjCompiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + problem.getSpjLanguage());
// 如果不存在该已经编译好的特批程序则需要再次进行编译 版本变动也需要重新编译
if (!FileUtil.exist(Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator +
problem.getId() + File.separator + spjCompiler.getExeName())
|| !problem.getCaseVersion().equals(version)) {
return Compiler.compileSpj(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage());
}
Constants.JudgeMode judgeMode = Constants.JudgeMode.getJudgeMode(problem.getJudgeMode());
Constants.CompileConfig compiler;
String filePath;
switch (judgeMode) {
case DEFAULT:
return true;
case SPJ:
compiler = Constants.CompileConfig.getCompilerByLanguage("SPJ-" + problem.getSpjLanguage());
filePath = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator +
problem.getId() + File.separator + compiler.getExeName();
// 如果不存在该已经编译好的程序则需要再次进行编译 版本变动也需要重新编译
if (!FileUtil.exist(filePath) || !problem.getCaseVersion().equals(version)) {
return Compiler.compileSpj(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage(),
JudgeUtils.getProblemExtraFileMap(problem, "judge"));
}
break;
case INTERACTIVE:
compiler = Constants.CompileConfig.getCompilerByLanguage("INTERACTIVE-" + problem.getSpjLanguage());
filePath = Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent() + File.separator +
problem.getId() + File.separator + compiler.getExeName();
// 如果不存在该已经编译好的程序则需要再次进行编译 版本变动也需要重新编译
if (!FileUtil.exist(filePath) || !problem.getCaseVersion().equals(version)) {
return Compiler.compileInteractive(problem.getSpjCode(), problem.getId(), problem.getSpjLanguage(),
JudgeUtils.getProblemExtraFileMap(problem, "judge"));
}
break;
default:
throw new RuntimeException("The problem mode is error:" + judgeMode);
}
return true;
}

View File

@ -38,14 +38,14 @@ public class ProblemTestCaseUtils {
public JSONObject initTestCase(List<HashMap<String, Object>> testCases,
Long problemId,
String version,
Boolean isSpj) throws SystemError, UnsupportedEncodingException {
String mode) throws SystemError, UnsupportedEncodingException {
if (testCases == null || testCases.size() == 0) {
throw new SystemError("题号为:" + problemId + "的评测数据为空!", null, "The test cases does not exist.");
}
JSONObject result = new JSONObject();
result.set("isSpj", isSpj);
result.set("mode", mode);
result.set("version", version);
result.set("testCasesSize", testCases.size());
@ -74,8 +74,8 @@ public class ProblemTestCaseUtils {
FileWriter outFile = new FileWriter(testCasesDir + "/" + outputName, CharsetUtil.UTF_8);
outFile.write(outputData);
// spj是根据特判程序输出判断结果所以无需初始化测试数据
if (!isSpj) {
// spj或interactive是根据特判程序输出判断结果所以无需初始化测试数据
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
// 原数据MD5
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(outputData.getBytes()));
// 原数据大小
@ -98,13 +98,13 @@ public class ProblemTestCaseUtils {
}
// 本地有文件进行数据初始化 生成json文件
public JSONObject initLocalTestCase(Boolean isSpj,
public JSONObject initLocalTestCase(String mode,
String version,
String testCasesDir,
List<ProblemCase> problemCaseList) {
JSONObject result = new JSONObject();
result.set("isSpj", isSpj);
result.set("mode", mode);
result.set("version", version);
result.set("testCasesSize", problemCaseList.size());
result.set("testCases", new JSONArray());
@ -120,8 +120,8 @@ public class ProblemTestCaseUtils {
FileReader readFile = new FileReader(testCasesDir + File.separator + problemCase.getOutput(), CharsetUtil.UTF_8);
String output = readFile.readString().replaceAll("\r\n", "\n");
// spj是根据特判程序输出判断结果所以无需初始化测试数据
if (!isSpj) {
// spj或interactive是根据特判程序输出判断结果所以无需初始化测试数据
if (Constants.JudgeMode.DEFAULT.getMode().equals(mode)) {
// 原数据MD5
jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(output.getBytes()));
// 原数据大小
@ -144,23 +144,23 @@ public class ProblemTestCaseUtils {
// 获取指定题目的info数据
public JSONObject loadTestCaseInfo(Long problemId, String testCasesDir, String version, Boolean isSpj) throws SystemError, UnsupportedEncodingException {
public JSONObject loadTestCaseInfo(Long problemId, String testCasesDir, String version, String mode) throws SystemError, UnsupportedEncodingException {
if (FileUtil.exist(testCasesDir + File.separator + "info")) {
FileReader fileReader = new FileReader(testCasesDir + File.separator + "info", CharsetUtil.UTF_8);
String infoStr = fileReader.readString();
JSONObject testcaseInfo = JSONUtil.parseObj(infoStr);
// 测试样例被改动需要重新生成
if (!testcaseInfo.getStr("version", null).equals(version)) {
return tryInitTestCaseInfo(testCasesDir, problemId, version, isSpj);
return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);
}
return testcaseInfo;
} else {
return tryInitTestCaseInfo(testCasesDir, problemId, version, isSpj);
return tryInitTestCaseInfo(testCasesDir, problemId, version, mode);
}
}
// 若没有测试数据则尝试从数据库获取并且初始化到本地如果数据库中该题目测试数据为空rsync同步也出了问题则直接判系统错误
public JSONObject tryInitTestCaseInfo(String testCasesDir, Long problemId, String version, Boolean isSpj) throws SystemError, UnsupportedEncodingException {
public JSONObject tryInitTestCaseInfo(String testCasesDir, Long problemId, String version, String mode) throws SystemError, UnsupportedEncodingException {
QueryWrapper<ProblemCase> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("pid", problemId);
@ -180,7 +180,7 @@ public class ProblemTestCaseUtils {
if (FileUtil.isEmpty(new File(testCasesDir))) { //如果本地对应文件夹也为空说明文件丢失了
throw new SystemError("problemID:[" + problemId + "] test case has not found.", null, null);
} else {
return initLocalTestCase(isSpj, version, testCasesDir, problemCases);
return initLocalTestCase(mode, version, testCasesDir, problemCases);
}
} else {
@ -194,7 +194,7 @@ public class ProblemTestCaseUtils {
testCases.add(tmp);
}
return initTestCase(testCases, problemId, version, isSpj);
return initTestCase(testCases, problemId, version, mode);
}
}

View File

@ -1,7 +1,6 @@
package top.hcode.hoj.judge;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONNull;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
@ -11,6 +10,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import top.hcode.hoj.common.exception.SystemError;
@ -19,6 +19,7 @@ import top.hcode.hoj.util.Constants;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: Himit_ZH
@ -74,7 +75,7 @@ public class SandboxRun {
private static final int MEMORY_LIMIT_MB = 512;
private static final int STACK_LIMIT_MB = 128;
private static final int STACK_LIMIT_MB = 256;
private SandboxRun() {
@ -200,9 +201,38 @@ public class SandboxRun {
COMPILE_FILES.put(stderr);
}
public static JSONArray compile(Long maxCpuTime, Long maxRealTime, Long maxMemory, Long maxStack, String srcName, String exeName,
List<String> args, List<String> envs, String code, Boolean needCopyOutCached,
Boolean needCopyOutExe, String copyOutDir) throws SystemError {
/**
* @param maxCpuTime 最大编译的cpu时间 ms
* @param maxRealTime 最大编译的真实时间 ms
* @param maxMemory 最大编译的空间 b
* @param maxStack 最大编译的栈空间 b
* @param srcName 编译的源文件名字
* @param exeName 编译生成的exe文件名字
* @param args 编译的cmd参数
* @param envs 编译的环境变量
* @param code 编译的源代码
* @param extraFiles 编译所需的额外文件 key:文件名value:文件内容
* @param needCopyOutCached 是否需要生成编译后的用户程序exe文件
* @param needCopyOutExe 是否需要生成用户程序的缓存文件即生成用户程序id
* @param copyOutDir 生成编译后的用户程序exe文件的指定路径
* @MethodName compile
* @Description 编译运行
* @Return
* @Since 2022/1/3
*/
public static JSONArray compile(Long maxCpuTime,
Long maxRealTime,
Long maxMemory,
Long maxStack,
String srcName,
String exeName,
List<String> args,
List<String> envs,
String code,
HashMap<String, String> extraFiles,
Boolean needCopyOutCached,
Boolean needCopyOutExe,
String copyOutDir) throws SystemError {
JSONObject cmd = new JSONObject();
cmd.set("args", args);
cmd.set("env", envs);
@ -215,10 +245,21 @@ public class SandboxRun {
cmd.set("procLimit", maxProcessNumber);
cmd.set("stackLimit", maxStack);
JSONObject file = new JSONObject();
file.set("content", code);
JSONObject fileContent = new JSONObject();
fileContent.set("content", code);
JSONObject copyIn = new JSONObject();
copyIn.set(srcName, file);
copyIn.set(srcName, fileContent);
if (extraFiles != null) {
for (Map.Entry<String, String> entry : extraFiles.entrySet()) {
if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(entry.getValue())) {
JSONObject content = new JSONObject();
content.set("content", entry.getValue());
copyIn.set(entry.getKey(), content);
}
}
}
cmd.set("copyIn", copyIn);
cmd.set("copyOut", new JSONArray().put("stdout").put("stderr"));
@ -241,8 +282,29 @@ public class SandboxRun {
}
public static JSONArray testCase(List<String> args, List<String> envs, String testCasePath, Long maxTime, Long maxOutputSize,
Integer maxStack, String exeName, String fileId) throws SystemError {
/**
* @param args 普通评测运行cmd的命令参数
* @param envs 普通评测运行的环境变量
* @param testCasePath 题目数据的输入文件路径
* @param maxTime 评测的最大限制时间 ms
* @param maxOutputSize 评测的最大输出大小 kb
* @param maxStack 评测的最大限制栈空间 mb
* @param exeName 评测的用户程序名称
* @param fileId 评测的用户程序文件id
* @MethodName testCase
* @Description 普通评测
* @Return JSONArray
* @Since 2022/1/3
*/
public static JSONArray testCase(List<String> args,
List<String> envs,
String testCasePath,
Long maxTime,
Long maxOutputSize,
Integer maxStack,
String exeName,
String fileId) throws SystemError {
JSONObject cmd = new JSONObject();
cmd.set("args", args);
cmd.set("env", envs);
@ -292,6 +354,22 @@ public class SandboxRun {
}
/**
* @param args 特殊判题的运行cmd命令参数
* @param envs 特殊判题的运行环境变量
* @param userOutputFilePath 用户程序输出文件的路径
* @param userOutputFileName 用户程序输出文件的名字
* @param testCaseInputFilePath 题目数据的输入文件的路径
* @param testCaseInputFileName 题目数据的输入文件的名字
* @param testCaseOutputFilePath 题目数据的输出文件的路径
* @param testCaseOutputFileName 题目数据的输出文件的路径
* @param spjExeSrc 特殊判题的exe文件的路径
* @param spjExeName 特殊判题的exe文件的名字
* @MethodName spjCheckResult
* @Description 特殊判题的评测
* @Return JSONArray
* @Since 2022/1/3
*/
public static JSONArray spjCheckResult(List<String> args,
List<String> envs,
String userOutputFilePath,
@ -302,6 +380,7 @@ public class SandboxRun {
String testCaseOutputFileName,
String spjExeSrc,
String spjExeName) throws SystemError {
JSONObject cmd = new JSONObject();
cmd.set("args", args);
cmd.set("env", envs);
@ -367,23 +446,41 @@ public class SandboxRun {
return result;
}
/*
交互跑题 暂时不启用
/**
* @param args cmd的命令参数 评测运行的命令
* @param envs 测评的环境变量
* @param userExeName 用户程序的名字
* @param userFileId 用户程序在编译后返回的id主要是对应内存中已编译后的文件
* @param userMaxTime 用户程序的最大测评时间 ms
* @param userMaxStack 用户程序的最大测评栈空间 mb
* @param testCaseInputPath 题目数据的输入文件路径
* @param testCaseInputFileName 题目数据的输入文件名字
* @param testCaseOutputFilePath 题目数据的输出文件路径
* @param testCaseOutputFileName 题目数据的输出文件名字
* @param interactArgs 交互程序运行的cmd命令参数
* @param interactEnvs 交互程序运行的环境变量
* @param interactExeSrc 交互程序的exe文件路径
* @param interactExeName 交互程序的exe文件名字
* @MethodName interactTestCase
* @Description 交互评测
* @Return JSONArray
* @Since 2022/1/3
*/
public static JSONArray interactTestCase(List<String> args,
List<String> envs,
String userExeName,
String userFileId,
Long userMaxTime,
Integer userMaxStack,
String testCaseInputPath,
String testCaseInputFileName,
Long maxTime,
Integer maxStack,
List<String> spjArgs,
List<String> spjEnvs,
String spjExeSrc,
String testCaseOutputFilePath,
String testCaseOutputFileName,
String spjExeName) throws SystemError {
List<String> interactArgs,
List<String> interactEnvs,
String interactExeSrc,
String interactExeName) throws SystemError {
/**
* 注意用户源代码需要先编译若是通过编译需要先将文件存入内存再利用管道判题同时特殊判题程序必须已编译且存在否则判题失败系统错误
@ -408,12 +505,12 @@ public class SandboxRun {
pipeInputCmd.set("files", JSONUtil.parseArray(inTmp, false));
// ms-->ns
pipeInputCmd.set("cpuLimit", maxTime * 1000 * 1000L);
pipeInputCmd.set("realCpuLimit", maxTime * 1000 * 1000L * 3);
pipeInputCmd.set("cpuLimit", userMaxTime * 1000 * 1000L);
pipeInputCmd.set("realCpuLimit", userMaxTime * 1000 * 1000L * 3);
// byte
pipeInputCmd.set("memoryLimit", MEMORY_LIMIT_MB * 1024 * 1024L);
pipeInputCmd.set("clockLimit", maxProcessNumber);
pipeInputCmd.set("stackLimit", maxStack * 1024 * 1024L);
pipeInputCmd.set("stackLimit", userMaxStack * 1024 * 1024L);
JSONObject exeFile = new JSONObject();
exeFile.set("fileId", userFileId);
@ -426,8 +523,8 @@ public class SandboxRun {
// 管道输出用户程序输出数据经过特殊判题程序后得到的最终输出结果
JSONObject pipeOutputCmd = new JSONObject();
pipeOutputCmd.set("args", spjArgs);
pipeOutputCmd.set("env", spjEnvs);
pipeOutputCmd.set("args", interactArgs);
pipeOutputCmd.set("env", interactEnvs);
JSONArray outFiles = new JSONArray();
@ -453,7 +550,7 @@ public class SandboxRun {
pipeOutputCmd.set("stackLimit", STACK_LIMIT_MB * 1024 * 1024L);
JSONObject spjExeFile = new JSONObject();
spjExeFile.set("src", spjExeSrc);
spjExeFile.set("src", interactExeSrc);
JSONObject stdInputFileSrc = new JSONObject();
stdInputFileSrc.set("src", testCaseInputPath);
@ -462,7 +559,7 @@ public class SandboxRun {
stdOutFileSrc.set("src", testCaseOutputFilePath);
JSONObject spjCopyIn = new JSONObject();
spjCopyIn.set(spjExeName, spjExeFile);
spjCopyIn.set(interactExeName, spjExeFile);
spjCopyIn.set(testCaseInputFileName, stdInputFileSrc);
spjCopyIn.set(testCaseOutputFileName, stdOutFileSrc);
@ -502,8 +599,10 @@ public class SandboxRun {
// 调用判题安全沙箱
JSONArray result = instance.run("/run", param);
JSONObject tmp = (JSONObject) result.get(0);
((JSONObject) result.get(0)).set("status", RESULT_MAP_STATUS.get(tmp.getStr("status")));
JSONObject userRes = (JSONObject) result.get(0);
JSONObject interactiveRes = (JSONObject) result.get(1);
userRes.set("status", RESULT_MAP_STATUS.get(userRes.getStr("status")));
interactiveRes.set("status", RESULT_MAP_STATUS.get(interactiveRes.getStr("status")));
return result;
}
@ -598,7 +697,7 @@ public class SandboxRun {
}
}]
3. SPJ
3. Interactive
{
"pipeMapping": [

View File

@ -0,0 +1,43 @@
package top.hcode.hoj.judge.entity;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @Author: Himit_ZH
* @Date: 2022/1/2 20:58
* @Description: 评测题目的传输类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Builder
public class JudgeDTO implements Serializable {
private static final long serialVersionUID = 666L;
/**
* 当前题目评测点的的编号
*/
private Integer testCaseId;
/**
* 当前题目评测点的输入文件的绝对路径
*/
private String testCaseInputPath;
/**
* 当前题目评测点的输出文件的绝对路径
*/
private String testCaseOutputPath;
/**
* 当前题目评测点的输出字符大小限制 kb
*/
private Long maxOutputSize;
}

View File

@ -0,0 +1,102 @@
package top.hcode.hoj.judge.entity;
import cn.hutool.json.JSONObject;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import top.hcode.hoj.util.Constants;
import java.io.Serializable;
import java.util.HashMap;
/**
* @Author: Himit_ZH
* @Date: 2022/1/3 11:53
* @Description: 一次评测全局通用的传输实体类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Builder
public class JudgeGlobalDTO implements Serializable {
private static final long serialVersionUID = 888L;
/**
* 当前评测题目的id
*/
private Long problemId;
/**
* 当前评测题目的模式
*/
private Constants.JudgeMode judgeMode;
/**
* 用户程序在沙盒编译后对应内存文件的id运行时需要传入
*/
private String userFileId;
/**
* 整个评测的工作目录
*/
private String runDir;
/**
* 判题沙盒评测程序的最大实际时间一般为题目最大限制时间+200ms
*/
private Long testTime;
/**
* 当前题目评测的最大时间限制 ms
*/
private Long maxTime;
/**
* 当前题目评测的最大空间限制 kb
*/
private Long maxMemory;
/**
* 当前题目评测的最大栈空间限制 mb
*/
private Integer maxStack;
/**
* 评测数据json内容
*/
private JSONObject testCaseInfo;
/**
* 交互程序或特判程序所需的额外文件 key:文件名value文件路径
*/
private HashMap<String,String> judgeExtraFiles;
/**
* 普通评测的命令配置
*/
Constants.RunConfig runConfig;
/**
* 特殊判题的命令配置
*/
Constants.RunConfig spjRunConfig;
/**
* 交互判题的命令配置
*/
Constants.RunConfig interactiveRunConfig;
/**
* 是否需要生成用户程序输出的文件
*/
private Boolean needUserOutputFile;
/**
* 是否需要自动移除评测数据的行末空格
*/
private Boolean removeEOLBlank;
}

View File

@ -0,0 +1,49 @@
package top.hcode.hoj.judge.entity;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* @Author: Himit_ZH
* @Date: 2022/1/3 15:27
* @Description: 单个测评结果实体类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Builder
public class SandBoxRes {
/**
* 单个程序的状态码
*/
private Integer status;
/**
* 单个程序的退出码
*/
private Integer exitCode;
/**
* 单个程序的运行所耗空间 kb
*/
private Long memory;
/**
* 单个程序的运行所耗时间 ms
*/
private Long time;
/**
* 单个程序的标准输出
*/
private String stdout;
/**
* 单个程序的错误信息
*/
private String stderr;
}

View File

@ -0,0 +1,143 @@
package top.hcode.hoj.judge.task;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.AbstractJudge;
import top.hcode.hoj.judge.SandboxRun;
import top.hcode.hoj.judge.entity.JudgeDTO;
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
import top.hcode.hoj.judge.entity.SandBoxRes;
import top.hcode.hoj.util.Constants;
/**
* @Author: Himit_ZH
* @Date: 2022/1/2 21:18
* @Description: 普通评测
*/
@Component
public class DefaultJudge extends AbstractJudge {
@Override
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
// 调用安全沙箱使用测试点对程序进行测试
return SandboxRun.testCase(
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
judgeDTO.getTestCaseInputPath(),
judgeGlobalDTO.getTestTime(),
judgeDTO.getMaxOutputSize(),
judgeGlobalDTO.getMaxStack(),
runConfig.getExeName(),
judgeGlobalDTO.getUserFileId());
}
@Override
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
JSONObject result = new JSONObject();
StringBuilder errMsg = new StringBuilder();
// 如果测试跑题无异常
if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
// 对结果的时间损耗和空间损耗与题目限制做比较判断是否mle和tle
if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else {
// 与原测试数据输出的md5进行对比 AC或者是WA
JSONObject testcaseInfo = (JSONObject) ((JSONArray) judgeGlobalDTO.getTestCaseInfo().get("testCases")).get(judgeDTO.getTestCaseId() - 1);
result.set("status", compareOutput(sandBoxRes.getStdout(), judgeGlobalDTO.getRemoveEOLBlank(), testcaseInfo));
}
} else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (sandBoxRes.getExitCode() != 0) {
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
if (sandBoxRes.getExitCode() < 32) {
errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
} else {
errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
}
} else {
result.set("status", sandBoxRes.getStatus());
}
// b
result.set("memory", sandBoxRes.getMemory());
// ns->ms
result.set("time", sandBoxRes.getTime());
if (!StringUtils.isEmpty(sandBoxRes.getStdout())) {
// 对于当前测试样例用户程序的输出对应生成的文件
FileWriter stdWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".out");
stdWriter.write(sandBoxRes.getStdout());
}
if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
// 对于当前测试样例用户的错误提示生成对应文件
FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".err");
errWriter.write(sandBoxRes.getStderr());
// 同时记录错误信息
errMsg.append(sandBoxRes.getStderr());
}
// 记录该测试点的错误信息
if (!StringUtils.isEmpty(errMsg.toString())) {
result.set("errMsg", errMsg.toString());
}
if (judgeGlobalDTO.getNeedUserOutputFile()) { // 如果需要获取用户对于该题目的输出
result.set("output", sandBoxRes.getStdout());
}
return result;
}
@Override
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
return null;
}
// 根据评测结果与用户程序输出的字符串MD5进行对比
private Integer compareOutput(String userOutput, Boolean isRemoveEOLBlank, JSONObject testcaseInfo) {
// 如果当前题目选择默认去掉字符串末位空格
if (isRemoveEOLBlank) {
String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());
if (userOutputMd5.equals(testcaseInfo.getStr("EOFStrippedOutputMd5"))) {
return Constants.Judge.STATUS_ACCEPTED.getStatus();
}
} else { // 不选择默认去掉文末空格 与原数据进行对比
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());
if (userOutputMd5.equals(testcaseInfo.getStr("outputMd5"))) {
return Constants.Judge.STATUS_ACCEPTED.getStatus();
}
}
// 如果不AC,进行PE判断否则为WA
String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll("\\s+", "").getBytes());
if (userOutputMd5.equals(testcaseInfo.getStr("allStrippedOutputMd5"))) {
return Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus();
} else {
return Constants.Judge.STATUS_WRONG_ANSWER.getStatus();
}
}
// 去除行末尾空白符
private String rtrim(String value) {
if (value == null) return null;
StringBuilder sb = new StringBuilder();
String[] strArr = value.split("\n");
for (String str : strArr) {
sb.append(str.replaceAll("\\s+$", "")).append("\n");
}
return sb.toString().replaceAll("\\s+$", "");
}
}

View File

@ -0,0 +1,165 @@
package top.hcode.hoj.judge.task;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.AbstractJudge;
import top.hcode.hoj.judge.SandboxRun;
import top.hcode.hoj.judge.entity.JudgeDTO;
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
import top.hcode.hoj.judge.entity.SandBoxRes;
import top.hcode.hoj.util.Constants;
import java.io.File;
/**
* @Author: Himit_ZH
* @Date: 2022/1/2 23:24
* @Description: 交互评测
*/
@Component
public class InteractiveJudge extends AbstractJudge {
@Override
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
Constants.RunConfig interactiveRunConfig = judgeGlobalDTO.getInteractiveRunConfig();
// 交互程序的路径
String interactiveExeSrc = Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent()
+ File.separator + judgeGlobalDTO.getProblemId() + File.separator + interactiveRunConfig.getExeName();
String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
// 其实交互题不存在用户输出文件的为了统一格式造了个名字
String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
JSONArray jsonArray = SandboxRun.interactTestCase(
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
runConfig.getExeName(),
judgeGlobalDTO.getUserFileId(),
judgeGlobalDTO.getTestTime(),
judgeGlobalDTO.getMaxStack(),
judgeDTO.getTestCaseInputPath(),
testCaseInputFileName,
judgeDTO.getTestCaseOutputPath(),
testCaseOutputFileName,
parseRunCommand(interactiveRunConfig.getCommand(), interactiveRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
interactiveRunConfig.getEnvs(),
interactiveExeSrc,
interactiveRunConfig.getExeName());
return jsonArray;
}
@Override
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
return null;
}
@Override
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
JSONObject result = new JSONObject();
// 记录错误信息
StringBuilder errMsg = new StringBuilder();
int userExitCode = userSandBoxRes.getExitCode();
if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
// 如果运行超过题目限制时间直接TLE
if (userSandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (userSandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) { // 如果运行超过题目限制空间直接MLE
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else {
// 根据交互程序的退出状态码及输出进行判断
JSONObject interactiveCheckRes = checkInteractiveRes(interactiveSandBoxRes);
int code = interactiveCheckRes.getInt("code");
if (code == SPJ_WA) {
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
} else if (code == SPJ_AC) {
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
} else if (code == SPJ_PE) {
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
} else {
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
}
String spjErrMsg = interactiveCheckRes.getStr("errMsg");
if (!StringUtils.isEmpty(spjErrMsg)) {
errMsg.append(spjErrMsg).append(" ");
}
}
} else if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if ((userExitCode != 0 && userExitCode != 13) || (userExitCode == 13 && interactiveSandBoxRes.getExitCode() == 0)) {
// Broken Pipe
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
if (userExitCode < 32) {
errMsg.append(String.format("Your program return exitCode: %s (%s)\n", userExitCode, SandboxRun.signals.get(userExitCode)));
} else {
errMsg.append(String.format("Your program return exitCode: %s\n", userExitCode));
}
} else {
result.set("status", interactiveSandBoxRes.getStatus());
errMsg.append(interactiveSandBoxRes.getStderr()).append(" ");
if (interactiveSandBoxRes.getExitCode() !=0 && !StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
errMsg.append(String.format("Interactive program exited with code: %s",interactiveSandBoxRes.getExitCode()));
}
}
// kb
result.set("memory", userSandBoxRes.getMemory());
// ms
result.set("time", userSandBoxRes.getTime());
// 记录该测试点的错误信息
if (!StringUtils.isEmpty(errMsg.toString())) {
result.set("errMsg", errMsg.toString());
}
return result;
}
private JSONObject checkInteractiveRes(SandBoxRes interactiveSandBoxRes) {
JSONObject result = new JSONObject();
int exitCode = interactiveSandBoxRes.getExitCode();
// 获取跑题用户输出或错误输出
if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
result.set("errMsg", interactiveSandBoxRes.getStderr());
}
// 如果程序无异常
if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
result.set("code", SPJ_AC);
} else {
result.set("code", exitCode);
}
} else if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_RUNTIME_ERROR.getStatus())) {
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
result.set("code", exitCode);
} else {
if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
// 适配testlib.h 根据错误信息前缀判断
return parseTestLibErr(interactiveSandBoxRes.getStderr());
} else {
result.set("code", SPJ_ERROR);
}
}
} else {
result.set("code", SPJ_ERROR);
}
return result;
}
}

View File

@ -0,0 +1,196 @@
package top.hcode.hoj.judge.task;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.judge.AbstractJudge;
import top.hcode.hoj.judge.SandboxRun;
import top.hcode.hoj.judge.entity.JudgeDTO;
import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
import top.hcode.hoj.judge.entity.SandBoxRes;
import top.hcode.hoj.util.Constants;
import java.io.File;
/**
* @Author: Himit_ZH
* @Date: 2022/1/2 22:23
* @Description: 特殊判题 支持testlib
*/
@Component
public class SpecialJudge extends AbstractJudge {
@Override
public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
// 调用安全沙箱使用测试点对程序进行测试
return SandboxRun.testCase(
parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
runConfig.getEnvs(),
judgeDTO.getTestCaseInputPath(),
judgeGlobalDTO.getTestTime(),
judgeDTO.getMaxOutputSize(),
judgeGlobalDTO.getMaxStack(),
runConfig.getExeName(),
judgeGlobalDTO.getUserFileId());
}
@Override
public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
return null;
}
@Override
public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
JSONObject result = new JSONObject();
StringBuilder errMsg = new StringBuilder();
// 如果测试跑题无异常
if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
// 对结果的时间损耗和空间损耗与题目限制做比较判断是否mle和tle
if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
} else {
// 对于当前测试样例用户程序的输出对应生成的文件
String userOutputFilePath = judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".out";
FileWriter stdWriter = new FileWriter(userOutputFilePath);
stdWriter.write(sandBoxRes.getStdout());
Constants.RunConfig spjRunConfig = judgeGlobalDTO.getSpjRunConfig();
// 特判程序的路径
String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator
+ judgeGlobalDTO.getProblemId() + File.separator + spjRunConfig.getExeName();
String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
// 进行spj程序运行比对
JSONObject spjResult = spjRunAndCheckResult(userOutputFilePath,
userOutputFileName,
judgeDTO.getTestCaseInputPath(),
testCaseInputFileName,
judgeDTO.getTestCaseOutputPath(),
testCaseOutputFileName,
spjExeSrc,
spjRunConfig);
int code = spjResult.getInt("code");
if (code == SPJ_WA) {
result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
} else if (code == SPJ_AC) {
result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
} else if (code == SPJ_PE) {
result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
} else {
result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
}
String spjErrMsg = spjResult.getStr("errMsg");
if (!StringUtils.isEmpty(spjErrMsg)) {
errMsg.append(spjErrMsg).append(" ");
}
}
} else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
} else if (sandBoxRes.getExitCode() != 0) {
result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
if (sandBoxRes.getExitCode() < 32) {
errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
} else {
errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
}
} else {
result.set("status", sandBoxRes.getStatus());
}
// b
result.set("memory", sandBoxRes.getMemory());
// ns->ms
result.set("time", sandBoxRes.getTime());
// 记录该测试点的错误信息
if (!StringUtils.isEmpty(errMsg.toString())) {
result.set("errMsg", errMsg.toString());
}
if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
// 同时记录错误信息
errMsg.append(sandBoxRes.getStderr());
// 对于当前测试样例用户的错误提示生成对应文件
FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".err");
errWriter.write(sandBoxRes.getStderr());
}
return result;
}
private JSONObject spjRunAndCheckResult(String userOutputFilePath,
String userOutputFileName,
String testCaseInputFilePath,
String testCaseInputFileName,
String testCaseOutputFilePath,
String testCaseOutputFileName,
String spjExeSrc,
Constants.RunConfig spjRunConfig) throws SystemError {
// 调用安全沙箱运行spj程序
JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(
parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
spjRunConfig.getEnvs(),
userOutputFilePath,
userOutputFileName,
testCaseInputFilePath,
testCaseInputFileName,
testCaseOutputFilePath,
testCaseOutputFileName,
spjExeSrc,
spjRunConfig.getExeName());
JSONObject result = new JSONObject();
JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);
// 获取跑题用户输出或错误输出
String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
if (!StringUtils.isEmpty(spjErrOut)) {
result.set("errMsg", spjErrOut);
}
// 退出状态码
int exitCode = spjJudgeResult.getInt("exitStatus");
// 如果测试跑题无异常
if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
result.set("code", SPJ_AC);
} else {
result.set("code", exitCode);
}
} else if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_RUNTIME_ERROR.getStatus()) {
if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
result.set("code", exitCode);
} else {
if (!StringUtils.isEmpty(spjErrOut)) {
// 适配testlib.h 根据错误信息前缀判断
return parseTestLibErr(spjErrOut);
} else {
result.set("code", SPJ_ERROR);
}
}
} else {
result.set("code", SPJ_ERROR);
}
return result;
}
}

View File

@ -6,6 +6,8 @@ import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.pojo.entity.judge.Judge;
import top.hcode.hoj.pojo.entity.problem.Problem;
import java.util.HashMap;
/**
* <p>
@ -19,7 +21,9 @@ public interface JudgeService extends IService<Judge> {
Judge Judge(Problem problem, Judge judge);
Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError;
Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String,String> extraFiles) throws SystemError;
Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap<String,String> extraFiles) throws SystemError;
void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score,Integer useTime);
}

View File

@ -78,10 +78,17 @@ public class JudgeServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements
return judge;
}
public Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError {
return Compiler.compileSpj(code, pid, spjLanguage);
@Override
public Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap<String, String> extraFiles) throws SystemError {
return Compiler.compileSpj(code, pid, spjLanguage, extraFiles);
}
@Override
public Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap<String, String> extraFiles) throws SystemError {
return Compiler.compileInteractive(code, pid, interactiveLanguage, extraFiles);
}
@Override
public void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score, Integer useTime) {

View File

@ -75,7 +75,7 @@ public class Constants {
CF_REMOTE_JUDGE_ACCOUNT("Codeforces Remote Judge Account");
private String name;
private final String name;
RemoteJudge(String remoteJudgeName) {
this.name = remoteJudgeName;
@ -108,6 +108,30 @@ public class Constants {
}
public enum JudgeMode {
DEFAULT("default"),
SPJ("spj"),
INTERACTIVE("interactive");
private final String mode;
JudgeMode(String mode) {
this.mode = mode;
}
public String getMode() {
return mode;
}
public static JudgeMode getJudgeMode(String mode){
for (JudgeMode judgeMode : JudgeMode.values()) {
if (judgeMode.getMode().equals(mode)) {
return judgeMode;
}
}
return null;
}
}
public enum JudgeDir {
@ -117,10 +141,12 @@ public class Constants {
SPJ_WORKPLACE_DIR("/judge/spj"),
INTERACTIVE_WORKPLACE_DIR("/judge/interactive"),
TMPFS_DIR("/w");
private String content;
private final String content;
JudgeDir(String content) {
this.content = content;
@ -134,7 +160,7 @@ public class Constants {
public static List<String> defaultEnv = Arrays.asList(
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=en_US.UTF-8",
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8","HOME=/w");
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "HOME=/w");
public static List<String> python3Env = Arrays.asList("LANG=en_US.UTF-8",
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "PYTHONIOENCODING=utf-8");
@ -143,7 +169,6 @@ public class Constants {
"GOCACHE=off", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8");
public static List<String> javaEnv = Arrays.asList("HOME=/w");
/*
{0} --> tmpfs_dir
{1} --> srcName
@ -158,7 +183,7 @@ public class Constants {
CPPWithO2("C++ With O2", "main.cpp", "main", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", javaEnv),
JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", defaultEnv),
PYTHON2("Python2", "main.py", "main.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python -m py_compile ./{1}", defaultEnv),
@ -166,20 +191,24 @@ public class Constants {
GOLANG("Golang", "main.go", "main", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/go build -o {2} {1}", defaultEnv),
CS("C#","Main.cs","main",5000L,10000L,512 * 1024 * 1024L,"/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}",defaultEnv),
CS("C#", "Main.cs", "main", 5000L, 10000L, 512 * 1024 * 1024L, "/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}", defaultEnv),
SPJ_C("SPJ-C", "spj.c", "spj", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
private String language;
private String srcName;
private String exeName;
private Long maxCpuTime;
private Long maxRealTime;
private Long maxMemory;
private String command;
private List<String> envs;
INTERACTIVE_C("INTERACTIVE-C", "interactive.c", "interactive", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
INTERACTIVE_CPP("INTERACTIVE-C++", "interactive.cpp", "interactive", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
private final String language;
private final String srcName;
private final String exeName;
private final Long maxCpuTime;
private final Long maxRealTime;
private final Long maxMemory;
private final String command;
private final List<String> envs;
CompileConfig(String language, String srcName, String exeName, Long maxCpuTime, Long maxRealTime, Long maxMemory,
String command, List<String> envs) {
@ -264,12 +293,16 @@ public class Constants {
SPJ_C("SPJ-C", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv);
SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
private String language;
private String command;
private String exeName;
private List<String> envs;
INTERACTIVE_C("INTERACTIVE-C", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv),
INTERACTIVE_CPP("INTERACTIVE-C++", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv);
private final String language;
private final String command;
private final String exeName;
private final List<String> envs;
RunConfig(String language, String command, String exeName, List<String> envs) {
this.language = language;

View File

@ -1,8 +1,11 @@
package top.hcode.hoj.util;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import cn.hutool.json.JSONUtil;
import org.springframework.util.StringUtils;
import top.hcode.hoj.pojo.entity.problem.Problem;
import java.io.File;
import java.util.*;
/**
* @Author: Himit_ZH
@ -11,7 +14,21 @@ import java.util.StringTokenizer;
*/
public class JudgeUtils {
public static List<String> translateCommandline(String toProcess){
@SuppressWarnings("All")
public static HashMap<String, String> getProblemExtraFileMap(Problem problem, String type) {
if ("user".equals(type)) {
if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
return (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
}
} else if ("judge".equals(type)) {
if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
return (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
}
}
return null;
}
public static List<String> translateCommandline(String toProcess) {
if (toProcess != null && !toProcess.isEmpty()) {
int state = 0;
StringTokenizer tok = new StringTokenizer(toProcess, "\"' ", true);
@ -19,10 +36,10 @@ public class JudgeUtils {
StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
while(true) {
while(tok.hasMoreTokens()) {
while (true) {
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch(state) {
switch (state) {
case 1:
if ("'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
@ -71,4 +88,5 @@ public class JudgeUtils {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,40 @@
package top.hcode.hoj.pojo.entity.judge;
import lombok.Data;
import java.util.HashMap;
/**
* @Author: Himit_ZH
* @Date: 2021/2/6 14:42
* @Description:
*/
@Data
public class CompileDTO {
/**
* 编译的源代码
*/
private String code;
/**
* 编译的源代码相关的题目id
*/
private Long pid;
/**
* 编译的源代码所选语言
*/
private String language;
/**
* 调用判题机的凭证
*/
private String token;
/**
* 编译所需的额外文件key:文件名,value:文件内容
*/
private HashMap<String,String> extraFiles;
}

View File

@ -1,21 +0,0 @@
package top.hcode.hoj.pojo.entity.judge;
import lombok.Data;
/**
* @Author: Himit_ZH
* @Date: 2021/2/6 14:42
* @Description:
*/
@Data
public class CompileSpj {
private String spjSrc;
private Long pid;
private String spjLanguage;
private String token;
}

View File

@ -42,6 +42,9 @@ public class Problem implements Serializable {
@ApiModelProperty(value = "0为ACM,1为OI")
private Integer type;
@ApiModelProperty(value = "default,spj,interactive")
private String judgeMode;
@ApiModelProperty(value = "单位ms")
private Integer timeLimit;
@ -78,20 +81,28 @@ public class Problem implements Serializable {
@ApiModelProperty(value = "默认为1公开2为私有3为比赛中")
private Integer auth;
@ApiModelProperty(value = "当该题目为io题目时的分数")
@ApiModelProperty(value = "当该题目为oi题目时的分数")
private Integer ioScore;
@ApiModelProperty(value = "该题目对应的相关提交代码,用户是否可用分享")
private Boolean codeShare;
@ApiModelProperty(value = "特判程序的代码 空代表无特判")
@ApiModelProperty(value = "特判程序或交互程序的代码")
@TableField(value="spj_code",updateStrategy = FieldStrategy.IGNORED)
private String spjCode;
@ApiModelProperty(value = "特判程序的语言")
@ApiModelProperty(value = "特判程序或交互程序的语言")
@TableField(value="spj_language",updateStrategy = FieldStrategy.IGNORED)
private String spjLanguage;
@ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
@TableField(value="user_extra_file",updateStrategy = FieldStrategy.IGNORED)
private String userExtraFile;
@ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
@TableField(value="judge_extra_file",updateStrategy = FieldStrategy.IGNORED)
private String judgeExtraFile;
@ApiModelProperty(value = "是否默认去除用户代码的每行末尾空白符")
private Boolean isRemoveEndBlank;

View File

@ -689,7 +689,7 @@ footer h1 {
box-shadow: inset 0 0 12px rgb(219 219 219);
}
.markdown-body p {
font-size: 14px;
font-size: 15px;
word-wrap: break-word;
word-break: break-word;
line-height: 1.8;

View File

@ -1007,6 +1007,11 @@ const adminApi = {
data
})
},
compileInteractive(data){
return ajax('/api/admin/problem/compile-interactive', 'post', {
data
})
},
admin_addTag (data) {
return ajax('/api/admin/tag', 'post', {

View File

@ -47,7 +47,7 @@ export default {
</script>
<style scoped>
.tit .accordion {
.accordion {
border: 1px solid #eaeefb;
}
.accordion header {

View File

@ -0,0 +1,146 @@
<template>
<div class="accordion">
<el-tag
:key="index"
v-for="(value, key, index) in files"
closable
class="file"
:disable-transitions="false"
@close="deleteFile(key)"
@click="openFileDialog(key, value)"
>
{{ key }}
</el-tag>
<el-button
class="button-new-file"
size="small"
@click="openFileDialog('', '')"
>+ New File</el-button
>
<el-dialog
:width="dialogWith"
:visible.sync="upsertFileDialogVisible"
@close-on-click-modal="false"
>
<el-form>
<el-form-item :label="$t('m.File_Name')" required>
<el-input
v-model="fileName"
size="small"
placeholder="******.h"
></el-input>
</el-form-item>
<el-form-item :label="$t('m.File_Content')" required>
<code-mirror v-model="fileContent"></code-mirror>
</el-form-item>
<el-form-item style="text-align:center;margin-top:10px">
<el-button type="primary" @click="upsertFile"
>{{ $t('m.Save') }}
</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import CodeMirror from '@/components/admin/CodeMirror.vue';
import myMessage from '@/common/message';
export default {
name: 'Accordion',
components: {
CodeMirror,
},
props: {
files: {
type: Array,
required: true,
},
type: {
type: String,
required: true,
},
},
mounted() {
let screenWidth = window.screen.width;
if (screenWidth < 768) {
this.dialogWith = '100%';
} else {
this.dialogWith = '70%';
}
},
data() {
return {
upsertFileDialogVisible: false,
upsertTagLoading: false,
fileName: '',
fileOldName: '',
fileContent: '',
dialogWith: '70%',
};
},
methods: {
deleteFile(fileName) {
this.$confirm(this.$i18n.t('m.Delete_Extra_File_Tips'), 'Tips', {
type: 'warning',
}).then(
() => {
this.$emit('deleteFile', this.type, fileName);
},
() => {}
);
},
openFileDialog(name, content) {
this.fileName = name;
this.fileContent = content;
this.fileOldName = name;
this.upsertFileDialogVisible = true;
},
upsertFile() {
if (!this.fileName) {
myMessage.error(
this.$i18n.t('m.File_Name') + ' ' + this.$i18n.t('m.is_required')
);
return;
}
if (!this.fileContent) {
myMessage.error(
this.$i18n.t('m.File_Content') + ' ' + this.$i18n.t('m.is_required')
);
return;
}
this.$emit(
'upsertFile',
this.type,
this.fileName,
this.fileOldName,
this.fileContent
);
this.upsertFileDialogVisible = false;
},
},
};
</script>
<style scoped>
.accordion {
border: 1px solid #eaeefb;
}
.file {
margin: 10px;
cursor: pointer;
}
.button-new-file {
height: 32px;
line-height: 30px;
padding-top: 0;
padding-bottom: 0;
margin: 10px;
}
/deep/.CodeMirror-scroll {
max-height: 300px;
}
</style>

View File

@ -32,7 +32,7 @@ export default {
return {
currentValue: '',
options: {
mode: 'text/x-csrc',
mode: 'text/x-c++src',
lineNumbers: true,
lineWrapping: false,
theme: 'solarized',
@ -56,11 +56,11 @@ export default {
props: {
value: {
type: String,
default: 'C',
default: '',
},
mode: {
type: String,
default: 'text/x-csrc',
default: 'text/x-c++src',
},
},
mounted() {
@ -102,6 +102,6 @@ export default {
.CodeMirror-scroll {
min-height: 300px;
max-height: 1000px;
max-height: 600px;
}
</style>

View File

@ -1,96 +0,0 @@
<template>
<div class="panel" :class="{'small': small}">
<header>
<div class="title">
<template v-if="$slots.title">
<slot name="title"></slot>
</template>
<template v-else>
{{title}}
</template>
</div>
<div class="header_right">
<slot name="header"></slot>
</div>
</header>
<div class="body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Panel',
props: {
title: {
type: String,
required: false
},
small: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped lang="less">
.panel {
margin-bottom: 20px;
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
&.small {
max-width: 830px;
min-width: 700px;
margin-left: 20px;
margin-top: 10px;
}
header {
position: relative;
z-index: 10;
> .title {
margin: 0;
color: #333;
border-color: #ddd;
font-size: 18px;
font-weight: 300;
letter-spacing: 0.025em;
height: 60px;
line-height: 45px;
padding: 10px 15px;
border-bottom: 1px solid #eee;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
> .header_right {
position: absolute;
top: 50%;
right: 20px;
transform: translate(0, -50%);
}
}
.body {
padding: 15px;
}
}
</style>
<style lang="less">
.panel-options {
background-color: transparent;
position: relative;
height: 50px;
button {
margin-top: 18px;
margin-right: 10px;
}
> .page {
position: absolute;
right:20px;
top: 20px;
}
}
</style>

View File

@ -168,7 +168,6 @@ export default {
showCursorWhenSelecting: true,
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true },
// extraKeys: { Ctrl: 'autocomplete' }, //
autoFocus: true,
matchBrackets: true, //
indentUnit: 4, //
styleActiveLine: true,

View File

@ -194,13 +194,18 @@ export const m = {
Example_Input:'Example Input',
Example_Output:'Example Output',
Add_Example: 'Add Example',
Special_Judge: 'Special Judge',
Special_Judge_Code: 'Special Judge Code',
Special_Judge_Tips1:'Why use special judge?',
Special_Judge_Tips2:'The output required by the problem may not be unique, and different results are allowed.',
Special_Judge_Tips3:'The output within a certain precision range is acceptable.',
Judge_Mode:'Judge Mode',
General_Judge:'General Judge',
Special_Judge:'Special Judge',
Interactive_Judge:'Interactive Judge',
Special_Judge_Code: 'Special Judge Program Code',
Interactive_Judge_Code:'Interactive Judge Program Code',
General_Judge_Mode_Tips:'1. General Judge: the contestant program reads the problem standard input file, executes the code logic to obtain the contestant\'s output, and compares the contents of the problem standard output file to obtain the problem judgment result',
Special_Judge_Mode_Tips:'2. Special Judge: the output results required by the problem may not be unique, and different results are allowed. Therefore, a special program is needed to read standard output, player output and standard input, and compare them to obtain the final judgment result',
Interactive_Judge_Mode_Tips:'3. Interactive Judge: the standard output of the interactive program is written to the standard input of the player program through the interactive channel, and the standard output of the player program is written to the standard input of the interactive program through the interactive channel. Both need to flush the output buffer',
Use_Special_Judge: 'Use Special Judge',
SPJ_language: 'SPJ language',
SPJ_Language: 'SPJ Program Language',
Interactive_Language:'Interactive Program Langugae',
Compile: 'Compile',
Code_Template: 'Code Template',
Type: 'Type',
@ -228,8 +233,13 @@ export const m = {
is_required:'is required!',
Score_must_be_greater_than_or_equal_to_0:'Score must be greater than or equal to 0!',
Score_must_be_an_integer:'Score must be an integer!',
Spj_Code:'Spj Code',
Spj_Code_not_Compile_Success:'Spj Code was not compiled successfully, please compile again!',
Spj_Or_Interactive_Code:'Spj Or Interactive Code',
Spj_Or_Interactive_Code_not_Compile_Success:'Spj Or Interactive Code was not compiled successfully, please compile again!',
Judge_Extra_File:'Judge Extra File',
Judge_Extra_File_Tips1:'1. User Program: Provide additional library files for user program',
Judge_Extra_File_Tips2:'2. Special Or Interactive Program: Provide additional library files for special or interactive programs',
User_Program:'User Program',
SPJ_Or_Interactive_Program:'Special Or Interactive Program',
// /views/admin/problem/tag
@ -342,4 +352,9 @@ export const m = {
Content:'Content',
Report_Content:'Report Content',
The_number_of_discussions_selected_cannot_be_empty:'The number of discussions selected cannot be empty',
// components/admin/AddExtraFile.vue
Delete_Extra_File_Tips:'Are you sure you want to delete this extra file?',
File_Name:'File Name',
File_Content:'File Content'
}

View File

@ -193,13 +193,17 @@ export const m = {
Example_Input:'样例输入',
Example_Output:'样例输出',
Add_Example: '添加样例',
Special_Judge: '特殊判题',
Special_Judge_Code:'特殊判题代码',
Special_Judge_Tips1:'为什么要使用特殊判题?',
Special_Judge_Tips2:'题目要求的输出结果可能不唯一,允许不同结果存在。',
Special_Judge_Tips3:'题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以。例如题目要求保留几位小数,输出结果后几位小数不相同也是正确的。',
Use_Special_Judge: '使用特殊判题',
SPJ_language: 'SPJ语言',
Judge_Mode:'判题模式',
General_Judge:'普通判题',
Special_Judge:'特殊判题',
Interactive_Judge:'交互判题',
Special_Judge_Code:'特殊判题程序代码',
Interactive_Judge_Code:'交互判题程序代码',
General_Judge_Mode_Tips:'1. 普通判题:选手程序读取题目标准输入文件,执行代码逻辑得到选手输出,对比题目标准输出文件内容得到判题结果',
Special_Judge_Mode_Tips:'2. 特殊判题:题目要求的输出结果可能不唯一,允许不同结果存在,所以需要一个特殊程序读取标准输出、选手输出和标准输入,进行对比得出最终判题结果',
Interactive_Judge_Mode_Tips:'3. 交互判题:交互程序的标准输出通过交互通道写到选手程序标准输入,选手程序的标准输出通过交互通道写到交互程序的标准输入,两者需要刷新输出缓冲区',
Interactive_Language:'交互判题程序语言',
SPJ_Language: '特殊判题程序语言',
Compile: '编译',
Code_Template: '代码模板',
Type: '类型',
@ -227,9 +231,13 @@ export const m = {
is_required:'不能为空!',
Score_must_be_greater_than_or_equal_to_0:'分数必须大于0',
Score_must_be_an_integer:'分数必须是整数!',
Spj_Code:'Spj代码',
Spj_Code_not_Compile_Success:'Spj代码没有编译成功请重新编译',
Spj_Or_Interactive_Code:'Spj或交互程序的代码',
Spj_Or_Interactive_Code_not_Compile_Success:'Spj或交互程序的代码没有编译成功请重新编译',
Judge_Extra_File:'评测额外文件',
Judge_Extra_File_Tips1:'1. 选手程序:给选手程序提供额外的库文件',
Judge_Extra_File_Tips2:'2. 特殊或交互程序:给特殊或交互程序提供额外的库文件',
User_Program:'选手程序',
SPJ_Or_Interactive_Program:'特殊或交互程序',
// /views/admin/problem/tag
Admin_Tag:'标签管理',
@ -340,4 +348,9 @@ export const m = {
Content:'内容',
Report_Content:'举报内容',
The_number_of_discussions_selected_cannot_be_empty:'勾选的讨论不能为空',
// components/admin/AddExtraFile.vue
Delete_Extra_File_Tips:'你是否确定要删除该额外文件?',
File_Name:'文件名字',
File_Content:'文件内容'
}

View File

@ -138,6 +138,11 @@
<Editor :value.sync="problem.output"></Editor>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
<Editor :value.sync="problem.hint"></Editor>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
@ -159,6 +164,20 @@
</el-select>
</el-form-item>
</el-col>
<el-col :md="4" :xs="24">
<el-form-item :label="$t('m.Type')">
<el-radio-group
v-model="problem.type"
:disabled="disableRuleType || problem.isRemote"
@change="problemTypeChange"
>
<el-radio :label="0">ACM</el-radio>
<el-radio :label="1">OI</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :md="4" :xs="24">
<el-form-item :label="$t('m.Code_Shareable')">
<el-switch
@ -169,7 +188,8 @@
</el-switch>
</el-form-item>
</el-col>
<el-col :md="16" :xs="24">
<el-col :md="12" :xs="24">
<el-form-item :label="$t('m.Tags')" required>
<el-tag
v-for="tag in problemTags"
@ -300,30 +320,87 @@
>{{ $t('m.Add_Example') }}
</el-button>
</div>
<template v-if="!problem.isRemote">
<div class="panel-title home-title">
{{ $t('m.Special_Judge') }}
{{ $t('m.Judge_Extra_File') }}
<el-popover placement="right" trigger="hover">
<p>{{ $t('m.Special_Judge_Tips1') }}</p>
<p>1. {{ $t('m.Special_Judge_Tips2') }}</p>
<p>2. {{ $t('m.Special_Judge_Tips3') }}</p>
<p>{{ $t('m.Judge_Extra_File_Tips1') }}</p>
<p>{{ $t('m.Judge_Extra_File_Tips2') }}</p>
<i slot="reference" class="el-icon-question"></i>
</el-popover>
</div>
<el-row :gutter="20">
<el-col :md="12" :xs="24">
<el-form-item>
<el-checkbox v-model="addUserExtraFile">{{
$t('m.User_Program')
}}</el-checkbox>
</el-form-item>
<el-form-item v-if="addUserExtraFile">
<AddExtraFile
:files.sync="userExtraFile"
type="user"
@upsertFile="upsertFile"
@deleteFile="deleteFile"
></AddExtraFile>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item>
<el-checkbox v-model="addJudgeExtraFile">{{
$t('m.SPJ_Or_Interactive_Program')
}}</el-checkbox>
</el-form-item>
<el-form-item v-if="addJudgeExtraFile">
<AddExtraFile
:files.sync="judgeExtraFile"
type="judge"
@upsertFile="upsertFile"
@deleteFile="deleteFile"
></AddExtraFile>
</el-form-item>
</el-col>
</el-row>
</template>
<template v-if="!problem.isRemote">
<div class="panel-title home-title">
{{ $t('m.Judge_Mode') }}
<el-popover placement="right" trigger="hover">
<p>{{ $t('m.General_Judge_Mode_Tips') }}</p>
<p>{{ $t('m.Special_Judge_Mode_Tips') }}</p>
<p>{{ $t('m.Interactive_Judge_Mode_Tips') }}</p>
<i slot="reference" class="el-icon-question"></i>
</el-popover>
</div>
<el-form-item label="" :error="error.spj">
<el-col :span="24">
<el-checkbox
v-model="problem.spj"
@click.native.prevent="switchSpj()"
>{{ $t('m.Use_Special_Judge') }}</el-checkbox
>
<el-radio-group v-model="problem.judgeMode" @change="switchMode">
<el-radio label="default">{{ $t('m.General_Judge') }}</el-radio>
<el-radio label="spj">{{ $t('m.Special_Judge') }}</el-radio>
<el-radio label="interactive">{{
$t('m.Interactive_Judge')
}}</el-radio>
</el-radio-group>
</el-col>
</el-form-item>
<el-form-item v-if="problem.spj">
<Accordion :title="$t('m.Special_Judge_Code')">
<el-form-item v-if="problem.judgeMode != 'default'">
<Accordion
:title="
problem.judgeMode == 'spj'
? $t('m.Special_Judge_Code')
: $t('m.Interactive_Judge_Code')
"
>
<template slot="header">
<span style="margin-right:5px;"
>{{ $t('m.SPJ_language') }}</span
>{{
problem.judgeMode == 'spj'
? $t('m.SPJ_Language')
: $t('m.Interactive_Language')
}}</span
>
<el-radio-group v-model="problem.spjLanguage">
<el-tooltip
@ -355,11 +432,8 @@
</el-form-item>
</template>
<el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
<Editor :value.sync="problem.hint"></Editor>
</el-form-item>
<el-form-item :label="$t('m.Code_Template')">
<div class="panel-title home-title">{{ $t('m.Code_Template') }}</div>
<el-form-item>
<el-row>
<el-col
:span="24"
@ -376,21 +450,6 @@
</el-row>
</el-form-item>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item :label="$t('m.Type')">
<el-radio-group
v-model="problem.type"
:disabled="disableRuleType || problem.isRemote"
@change="problemTypeChange"
>
<el-radio :label="0">ACM</el-radio>
<el-radio :label="1">OI</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="!problem.isRemote">
<div class="panel-title home-title">
{{ $t('m.Judge_Samples') }}
@ -582,11 +641,13 @@ import myMessage from '@/common/message';
import { PROBLEM_LEVEL_RESERVE } from '@/common/constants';
const Editor = () => import('@/components/admin/Editor.vue');
const Accordion = () => import('@/components/admin/Accordion.vue');
const AddExtraFile = () => import('@/components/admin/AddExtraFile.vue');
const CodeMirror = () => import('@/components/admin/CodeMirror.vue');
export default {
name: 'Problem',
components: {
Accordion,
AddExtraFile,
CodeMirror,
Editor,
},
@ -635,7 +696,6 @@ export default {
auth: 1,
codeShare: true,
examples: [], //
spj: false,
spjLanguage: '',
spjCode: '',
spjCompileOk: false,
@ -647,6 +707,9 @@ export default {
hint: '',
source: '',
cid: null,
judgeMode: 'default',
userExtraFile: '',
judgeExtraFile: '',
},
problemTags: [], //
problemLanguages: [], //
@ -677,6 +740,10 @@ export default {
spjCode: '',
spjLanguage: '',
},
addUserExtraFile: false,
addJudgeExtraFile: false,
userExtraFile: null,
judgeExtraFile: null,
};
},
mounted() {
@ -723,7 +790,6 @@ export default {
auth: 1,
codeShare: true,
examples: [],
spj: false,
spjLanguage: '',
spjCode: '',
spjCompileOk: false,
@ -735,6 +801,9 @@ export default {
hint: '',
source: '',
cid: null,
judgeMode: 'default',
userExtraFile: null,
judgeExtraFile: null,
};
this.contestID = contestID;
@ -829,13 +898,11 @@ export default {
}[this.routeName];
api[funcName](this.pid).then((problemRes) => {
let data = problemRes.data.data;
(data.spjCompileOk = false),
(data.uploadTestcaseDir = ''),
(data.testCaseScore = []);
data.spj = true;
data.spjCompileOk = false;
data.uploadTestcaseDir = '';
data.testCaseScore = [];
if (!data.spjCode) {
data.spjCode = '';
data.spj = false;
}
data.spjLanguage = data.spjLanguage || 'C';
this.spjRecord.spjLanguage = data.spjLanguage;
@ -844,7 +911,14 @@ export default {
this.problem['examples'] = utils.stringToExamples(data.examples);
this.problem['examples'][0]['isOpen'] = true;
this.testCaseUploaded = true;
if (this.problem.userExtraFile) {
this.addUserExtraFile = true;
this.userExtraFile = JSON.parse(this.problem.userExtraFile);
}
if (this.problem.judgeExtraFile) {
this.addJudgeExtraFile = true;
this.judgeExtraFile = JSON.parse(this.problem.judgeExtraFile);
}
api
.admin_getProblemCases(this.pid, this.problem.isUploadCase)
.then((res) => {
@ -891,7 +965,7 @@ export default {
});
},
switchSpj() {
switchMode(mode) {
if (this.testCaseUploaded) {
this.$confirm(this.$i18n.t('m.Change_Judge_Method'), 'Tips', {
confirmButtonText: this.$i18n.t('m.OK'),
@ -899,12 +973,12 @@ export default {
type: 'warning',
})
.then(() => {
this.problem.spj = !this.problem.spj;
this.problem.judgeMode = mode;
this.resetTestCase();
})
.catch(() => {});
} else {
this.problem.spj = !this.problem.spj;
this.problem.judgeMode = mode;
}
},
querySearch(queryString, cb) {
@ -965,6 +1039,34 @@ export default {
);
},
deleteFile(type, name) {
if (type == 'user') {
this.$delete(this.userExtraFile, name);
} else {
this.$delete(this.judgeExtraFile, name);
}
},
upsertFile(type, name, oldname, content) {
if (type == 'user') {
if (oldname && oldname != name) {
this.$delete(this.userExtraFile, oldname);
}
if (!this.userExtraFile) {
this.userExtraFile = {};
}
this.userExtraFile[name] = content;
} else {
if (oldname && oldname != name) {
this.$delete(this.judgeExtraFile, name);
}
if (!this.judgeExtraFile) {
this.judgeExtraFile = {};
}
this.judgeExtraFile[name] = content;
}
},
problemTypeChange(type) {
if (type == 1) {
let length = this.problemSamples.length;
@ -1036,7 +1138,7 @@ export default {
fileList[i].score = averSorce;
}
}
if (!fileList[i].output && this.problem.spj) {
if (!fileList[i].output) {
fileList[i].output = '-';
}
fileList[i].pid = this.problem.id;
@ -1052,11 +1154,16 @@ export default {
compileSPJ() {
let data = {
pid: this.problem.id,
spjSrc: this.problem.spjCode,
spjLanguage: this.problem.spjLanguage,
code: this.problem.spjCode,
language: this.problem.spjLanguage,
extraFiles: this.judgeExtraFile,
};
this.loadingCompile = true;
api.compileSPJ(data).then(
let apiMethodName = 'compileSPJ';
if (this.problem.judgeMode == 'interactive') {
apiMethodName = 'compileInteractive';
}
api[apiMethodName](data).then(
(res) => {
this.loadingCompile = false;
this.problem.spjCompileOk = true;
@ -1218,14 +1325,21 @@ export default {
myMessage.error(this.error.tags);
return;
}
let isChangeModeCode =
this.spjRecord.spjLanguage != this.problem.spjLanguage ||
this.spjRecord.spjCode != this.problem.spjCode;
if (this.problem.spj) {
if (this.problem.judgeMode != 'default') {
if (!this.problem.spjCode) {
this.error.spj =
this.$i18n.t('m.Spj_Code') + ' ' + this.$i18n.t('m.is_required');
this.$i18n.t('m.Spj_Or_Interactive_Code') +
' ' +
this.$i18n.t('m.is_required');
myMessage.error(this.error.spj);
} else if (!this.problem.spjCompileOk) {
this.error.spj = this.$i18n.t('m.Spj_Code_not_Compile_Success');
} else if (!this.problem.spjCompileOk && isChangeModeCode) {
this.error.spj = this.$i18n.t(
'm.Spj_Or_Interactive_Code_not_Compile_Success'
);
}
if (this.error.spj) {
myMessage.error(this.error.spj);
@ -1294,21 +1408,31 @@ export default {
}
let problemDto = {}; //
if (this.problem.spj) {
if (
this.spjRecord.spjLanguage != this.problem.spjLanguage ||
this.spjRecord.spjCode != this.problem.spjCode
) {
problemDto['changeSpj'] = true;
if (this.problem.judgeMode != 'default') {
if (isChangeModeCode) {
problemDto['changeModeCode'] = true;
}
} else {
// spj
// spj
if (!this.spjRecord.spjCode) {
problemDto['changeSpj'] = true;
problemDto['changeModeCode'] = true;
this.problem.spjCode = null;
this.problem.spjLanguage = null;
}
}
if (this.userExtraFile && Object.keys(this.userExtraFile).length != 0) {
this.problem.userExtraFile = JSON.stringify(this.userExtraFile);
} else {
this.problem.userExtraFile = null;
}
if (this.judgeExtraFile && Object.keys(this.judgeExtraFile).length != 0) {
this.problem.judgeExtraFile = JSON.stringify(this.judgeExtraFile);
} else {
this.problem.judgeExtraFile = null;
}
problemDto['problem'] = Object.assign({}, this.problem); //
problemDto.problem.examples = utils.examplesToString(
this.problem.examples
@ -1319,7 +1443,7 @@ export default {
problemDto['languages'] = problemLanguageList;
problemDto['isUploadTestCase'] = this.problem.isUploadCase;
problemDto['uploadTestcaseDir'] = this.problem.uploadTestcaseDir;
problemDto['isSpj'] = this.problem.spj;
problemDto['judgeMode'] = this.problem.judgeMode;
// 使
if (this.problem.isUploadCase) {

View File

@ -57,7 +57,7 @@ module.exports={
port: 8088, // 开发服务器运行端口号
proxy: {
'/api': { // 以'/api'开头的请求会被代理进行转发
target: 'http://localhost:6688', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
target: 'https://hdoi.cn', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
changeOrigin: true
}
},