Skip to content

Latest commit

 

History

History
214 lines (179 loc) · 10.6 KB

File metadata and controls

214 lines (179 loc) · 10.6 KB

1. 概述

TOJ-V2 是一个现代化的在线判题系统评测引擎,旨在提供高性能、高安全性和灵活的评测能力。与旧版引擎相比,V2引擎在以下几个核心方面进行了重构和增强:

  • 标准化测试数据格式:引入了 version: 2 的JSON结构,更清晰、更强大。
  • 灵活的评测策略:支持 binary(全对才得分)和 partial(部分得分)两种计分模式。
  • 多样化的校验器:内置了多种输出校验方式,如tokenfloatregex,并支持自定义script(特殊判题)。
  • 精细的测试点控制:支持分组、依赖关系、分值分配以及可见性控制。
  • 增强的安全沙箱:基于 isolate(Linux)等后端,有效隔离用户程序,防止恶意代码对系统造成影响。
  • 统一的代码库:将核心判题逻辑从业务逻辑中抽离,形成独立的engine模块,使系统更健壮、更易于维护。

2. 核心组件

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 数据访问层。封装了对数据库的所有操作,包括查询题目信息、记录提交、更新通过状态和限流检查等。

3. 工作流程

一次完整的评测请求(例如从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 结果];
Loading

4. 题目数据格式详解 (V2版本)

这是V2引擎最核心的部分。题目的所有测试数据、资源限制和评测策略都通过 test_cases 字段,以一个结构化的JSON对象存储。

4.1. 顶层结构

{
  "version": 2,
  "defaults": { ... },
  "policy": { ... },
  "groups": [ ... ],
  "cases": [ ... ]
}
  • version: 必须为 2,用于标识此格式的版本。
  • defaults: 定义该题目的全局默认配置。
  • policy: 定义该题目的评测策略。
  • groups: 定义测试点的分组信息。
  • cases: 定义所有具体的测试点。

4.2. defaults (默认配置)

"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 等
  }
}

4.3. policy (评测策略)

"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"]  // 允许提交的编程语言列表 (留空表示全部允许)
}

4.4. groups (测试点分组)

用于将相关的测试点组织在一起,可以设置子任务依赖和分值。

"groups": [
  {
    "id": "subtask1",              // 组ID,需唯一
    "name": "小数据",               // 组名
    "points": 30,                  // 该组总分 (如果组内测试点未单独赋分,则按此均分)
    "mode": "sum",                 // 计分模式: "sum" (累加), "all" (全部通过才得分)
    "depends_on": []               // 依赖的其他组ID,必须依赖的组全通过才执行本组
  }
]

注意: 如果 mode"all",则该组内的所有测试点必须全部通过,才能获得该组的全部分数。

4.5. cases (测试点列表)

每个测试点可以包含以下字段:

字段名 类型 描述 示例
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 旧版兼容。用于范围校验,推荐使用checkerdvx支持更复杂的元素级校验。 "[-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"
  }
}

5. 校验器 (Checker) 详解

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"

6. 特殊判题 (Special Judge)

当内置校验器无法满足需求时(例如,输出是一个浮点数序列,其长度不固定,或需要业务逻辑判断),可以使用特殊判题。

  1. 编写判题脚本:在题目的数据目录下创建一个脚本文件(如 spj.py)。
  2. 脚本协议:脚本会接收三个命令行参数:
    • input.txt:输入文件。
    • answer.txt:标准答案文件。
    • contestant.txt:用户输出文件。
  3. 脚本逻辑:脚本读取这三个文件,执行业务逻辑判断,最后打印结果。如果用户输出正确,脚本退出码应为0
  4. 输出格式:脚本可输出分数和反馈信息。例如:
    • score = 0.8:表示该测试点得80%的分。
    • 其他输出将作为反馈信息显示给用户。
  5. 配置测试点
    {
      "id": "custom-check",
      "checker": {
        "type": "script",
        "path": "spj.py",
        "cmd": "python"
      }
    }

7. 范围表达式 (Range Expression)

用于 rangefields 校验器,提供了一种强大的数值验证方式。

  • 比较运算>, <, >=, <=, ==, !=
  • 区间
    • [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之间。