TOJ-V2 是一个现代化的在线判题系统评测引擎,旨在提供高性能、高安全性和灵活的评测能力。与旧版引擎相比,V2引擎在以下几个核心方面进行了重构和增强:
- 标准化测试数据格式:引入了
version: 2的JSON结构,更清晰、更强大。 - 灵活的评测策略:支持
binary(全对才得分)和partial(部分得分)两种计分模式。 - 多样化的校验器:内置了多种输出校验方式,如
token、float、regex,并支持自定义script(特殊判题)。 - 精细的测试点控制:支持分组、依赖关系、分值分配以及可见性控制。
- 增强的安全沙箱:基于
isolate(Linux)等后端,有效隔离用户程序,防止恶意代码对系统造成影响。 - 统一的代码库:将核心判题逻辑从业务逻辑中抽离,形成独立的
engine模块,使系统更健壮、更易于维护。
V2引擎的主要组件如下,它们共同协作完成一次完整的评测任务。
| 组件名 (文件) | 职责描述 |
|---|---|
Endpoint.php |
入口控制器。处理HTTP请求,进行身份验证、限流,并调用Judge类执行评测,最后格式化并返回结果。 |
Judge.php |
评测主流程。负责编译用户代码、调度和执行所有测试点、汇总最终成绩。它是整个判题过程的核心协调者。 |
TestSuite.php |
测试套件加载器。解析题目的 test_cases JSON数据,将其加载成引擎可用的标准化测试点结构。 |
Checker.php |
答案校验器。包含多种内置的比较策略(如exact, token, float),并支持通过SpecialJudge调用外部脚本进行校验。 |
Sandbox/*.php |
沙箱后端。提供一个安全隔离的运行环境,用于编译和执行用户代码,并精确监控其资源(时间、内存、输出)使用情况。支持isolate, posix, windows等多种后端。 |
Language.php |
语言支持注册表。定义了所支持的编程语言(C++, Python, Java, PHP等),包括它们的编译、运行命令及环境配置。 |
Guard.php |
静态安全检查器。在编译前对用户代码进行静态分析,检测并拦截潜在的危险函数(如exec, eval),提高系统安全性。 |
Repository.php |
数据访问层。封装了对数据库的所有操作,包括查询题目信息、记录提交、更新通过状态和限流检查等。 |
一次完整的评测请求(例如从code-runner.php进入)遵循以下流程:
graph TD
A[用户提交代码] --> B(Endpoint::handleJudge);
B --> C{验证Token & 参数};
C -- 无效 --> D[返回错误];
C -- 有效 --> E[Repository: 查询题目 & 限流检查];
E -- 超限 --> F[返回限流错误];
E -- 通过 --> G[Repository: 创建提交记录];
G --> H[Judge::run 开始评测];
H --> I[语言适配 & 静态安全检查 Guard];
I -- 阻断 --> J[返回编译错误];
I -- 通过 --> K[编译用户代码];
K -- 失败 --> L[返回编译错误];
K -- 成功 --> M[加载测试套件 TestSuite];
M --> N[调度并执行所有测试点];
N --> O[对每个测试点: 沙箱运行 Sandbox];
O --> P[沙箱: 监控资源 & 生成输出];
P --> Q[Checker: 比对输出与答案];
Q --> R[汇总所有测试点结果];
R --> S[Repository: 更新提交记录 & 记录通过状态];
S --> T[Endpoint: 返回 JSON 结果];
这是V2引擎最核心的部分。题目的所有测试数据、资源限制和评测策略都通过 test_cases 字段,以一个结构化的JSON对象存储。
{
"version": 2,
"defaults": { ... },
"policy": { ... },
"groups": [ ... ],
"cases": [ ... ]
}version: 必须为2,用于标识此格式的版本。defaults: 定义该题目的全局默认配置。policy: 定义该题目的评测策略。groups: 定义测试点的分组信息。cases: 定义所有具体的测试点。
"defaults": {
"time_limit_ms": 2000, // 全局默认时间限制 (毫秒)
"memory_limit_kb": 262144, // 全局默认内存限制 (KB)
"output_limit_kb": 8192, // 全局默认输出限制 (KB)
"visibility": "summary", // 全局默认可见性: "public", "summary", "hidden"
"checker": { // 全局默认校验器
"type": "token" // 类型: token, float, exact, range, script 等
}
}"policy": {
"scoring": "partial", // 计分模式: "partial" (部分得分) 或 "binary" (全对才得分)
"reveal": "summary", // 反馈可见性: "summary" (摘要), "full" (完整), "none" (不显示)
"stop_on_first_failure": true, // 是否在遇到首个错误测试点时立即停止后续测试
"include_sample": true, // 是否将题目的示例输入/输出作为测试点 (通常为第一个)
"max_parallel": 0, // 最大并行评测数 (0为自动)
"languages": ["cpp", "python"] // 允许提交的编程语言列表 (留空表示全部允许)
}用于将相关的测试点组织在一起,可以设置子任务依赖和分值。
"groups": [
{
"id": "subtask1", // 组ID,需唯一
"name": "小数据", // 组名
"points": 30, // 该组总分 (如果组内测试点未单独赋分,则按此均分)
"mode": "sum", // 计分模式: "sum" (累加), "all" (全部通过才得分)
"depends_on": [] // 依赖的其他组ID,必须依赖的组全通过才执行本组
}
]注意: 如果
mode为"all",则该组内的所有测试点必须全部通过,才能获得该组的全部分数。
每个测试点可以包含以下字段:
| 字段名 | 类型 | 描述 | 示例 |
|---|---|---|---|
id |
string |
测试点唯一标识符。 | "case-1" |
group |
string |
所属组的ID,需与groups中定义的一致。 |
"subtask1" |
points |
float |
该测试点的分值。如果为0,则根据分组或剩余分数自动分配。 | 10.0 |
visibility |
string |
该测试点的可见性。"public"(公开), "summary"(摘要), "hidden"(隐藏)。 |
"public" |
in / in_file |
string |
输入数据。in为直接写字符串,in_file为数据文件相对于题目数据目录的路径。 |
"1 2" 或 "data/1.in" |
out / out_file |
string |
期望输出。out为直接写字符串,out_file为答案文件路径。 |
"3" 或 "data/1.out" |
checker |
object |
该测试点专用的校验器,会覆盖defaults中的配置。 |
{"type": "float", "eps": 1e-6} |
dv / dvx |
string/array |
旧版兼容。用于范围校验,推荐使用checker。dvx支持更复杂的元素级校验。 |
"[-3>9,-1>7]" |
time_limit_ms |
int |
覆盖默认的时间限制。 | 1000 |
memory_limit_kb |
int |
覆盖默认的内存限制。 | 131072 |
示例:一个完整的测试点
{
"id": "sample",
"group": "subtask1",
"points": 5,
"visibility": "public",
"in": "5\n10",
"out": "15",
"checker": {
"type": "token"
}
}V2引擎内置了多种校验器,能满足绝大多数需求。
类型 (type) |
描述 | 关键参数 |
|---|---|---|
exact |
逐字节比较,最严格。 | 无 |
trim |
去除首尾空白后比较。 | 无 |
token (默认) |
按空白符分词后比较,忽略多余空格和换行。是默认且最常用的类型。 | ignore_case: true (忽略大小写) |
lines |
逐行比较。 | ignore_case: true |
unordered_lines |
行集合比较,忽略各行顺序。 | ignore_case: true, keep_inner_space: true |
float |
浮点数比较。 | eps: 误差阈值 (默认1e-6), mode: "absolute", "relative", "both"(默认) |
range |
验证输出是否在指定范围内。 | expr: 范围表达式,如 "[0, 100]", ">=94", `"[0,10] |
regex |
用正则表达式匹配整个输出。 | pattern: 正则表达式, flags: 如 "i", "m" |
fields |
按列(字段)进行规则校验。 | rules: 规则数组, separator: 列分隔符 (默认空白符) |
script |
特殊判题。调用外部脚本(如Python)进行复杂的逻辑校验。 | path: 脚本相对路径, cmd: "python", "node", "php", "native" |
script |
特殊判题,调用外部脚本。 | path: 脚本相对路径, cmd: "python", "node", "php", "native" |
当内置校验器无法满足需求时(例如,输出是一个浮点数序列,其长度不固定,或需要业务逻辑判断),可以使用特殊判题。
- 编写判题脚本:在题目的数据目录下创建一个脚本文件(如
spj.py)。 - 脚本协议:脚本会接收三个命令行参数:
input.txt:输入文件。answer.txt:标准答案文件。contestant.txt:用户输出文件。
- 脚本逻辑:脚本读取这三个文件,执行业务逻辑判断,最后打印结果。如果用户输出正确,脚本退出码应为
0。 - 输出格式:脚本可输出分数和反馈信息。例如:
score = 0.8:表示该测试点得80%的分。- 其他输出将作为反馈信息显示给用户。
- 配置测试点:
{ "id": "custom-check", "checker": { "type": "script", "path": "spj.py", "cmd": "python" } }
用于 range 和 fields 校验器,提供了一种强大的数值验证方式。
- 比较运算:
>,<,>=,<=,==,!= - 区间:
[a, b]:闭区间,a <= x <= b(a, b):开区间,a < x < b[a, b):左闭右开(a, b]:左开右闭
- 逻辑运算:
&或and:逻辑与|或or:逻辑或
- 示例:
[0, 100]:在0到100之间。>=94 & <100:大于等于94且小于100。[-1e-3, 1e-3]:在 -0.001 到 0.001 之间。[0,10] | [90,100]:在0到10之间或在90到100之间。