From 1b6d9a8d4d43ae30a0f07e1b6f7b9150740c8951 Mon Sep 17 00:00:00 2001 From: Love98 Date: Wed, 21 Aug 2024 17:11:42 +0800 Subject: [PATCH] docs: update judge-mid and add problem-types --- docs/.vitepress/config.mjs | 8 +++ docs/dev-guide/backend/intro/problem-types.md | 9 ++++ docs/dev-guide/backend/judge-mid/index.md | 54 ++++++++++++++----- .../judge-mid/submission-&-self-test.md | 50 +++++++++++++---- 4 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 docs/dev-guide/backend/intro/problem-types.md diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 3118bdf..18c9e5b 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -19,6 +19,10 @@ export default withMermaid({ lastUpdated: true, + outline: { + level: [2, 4], + }, + search: { provider: 'local' } @@ -67,6 +71,10 @@ export default withMermaid({ { text: '快速开始', link: '/quick-start' + }, + { + text: '题目类型', + link: '/problem-types' } ] }, diff --git a/docs/dev-guide/backend/intro/problem-types.md b/docs/dev-guide/backend/intro/problem-types.md new file mode 100644 index 0000000..053783d --- /dev/null +++ b/docs/dev-guide/backend/intro/problem-types.md @@ -0,0 +1,9 @@ +# 题目类型 + +我们题目类型分成几大类: + +- 本地评测 (Local-Judge) +- 远端评测 (Remote-Judge) +- 人工评测 (Manual-Judge) + +具体的题目类型和评测方式由[评测中间件](../judge-mid/)提供。 diff --git a/docs/dev-guide/backend/judge-mid/index.md b/docs/dev-guide/backend/judge-mid/index.md index d780c8f..4674b2c 100644 --- a/docs/dev-guide/backend/judge-mid/index.md +++ b/docs/dev-guide/backend/judge-mid/index.md @@ -1,27 +1,55 @@ # 评测中间件 -为了支持多种评测机的接入,我们设计了评测中间件。评测中间件是一个独立的评测服务,它负责接收评测请求,将评测请求转发给评测机,然后将评测机的评测结果进行处理,并按照 `sastoj` 的格式返回给调用方并持久化。 +为了支持更多的题型、评测机以及评测方式,我们设计了评测中间件。 -## 测试点格式 +## 设计思路 -目前 `sastoj` 的测试点格式为 [rsjudge-test-cases-schema](https://github.com/Jisu-Woniu/rsjudge-test-cases-schema) 中定义的格式。 +题型的评测由独立的微服务或插件来实现。每个题型的微服务或插件包含其自身的业务逻辑、数据模型以及与之相关的评测规则。通过这种方式,新的题型可以独立开发和部署,不会影响到其他题型。 -## 评测机接入 +### 独立的数据模型 -所有的评测请求均存放在**消息队列**中,测试结果存放在 `Redis` 和**数据库**中,评测机接入需要监听消息队列,当有评测请求时,评测机需要从消息队列中获取评测请求,然后进行评测,并将评测结果返回。 +每个题型服务可以使用自己的数据表来存储特定于该题型的信息,而通用的题目信息(例如标题、题干、分数等)则保存在主题目服务的通用表中。这样一来,当引入新题型时,只需要在对应服务中创建相应的数据模型,而无需对核心题目表进行改动,也即不需要修改获取题目信息的接口和提交评测等接口。 -### 消息队列 +### 独立的业务逻辑 -默认的提交 `Channel` 为 `submission`,默认的自测 `Channel` 为 `self-test`。评测机需要监听这两个 `Channel`。 +每个题型的服务独立负责其评测逻辑和其他特定的操作。比如本地评测服务处理测试点和判题规则,远程评测服务处理与外部评测系统的交互,手判服务处理人工评审的逻辑。通过这种方式,可以减少不同服务之间的耦合,提高系统的可扩展性和可维护性,为未来引入新的题型提供了便利。 -### 缓存和持久化 +### 信息隔离与安全性 -评测机需要将评测结果存入缓存和数据库。数据库连接可以使用 `ent` 也可以使用其他 `ORM` 框架。 +为了避免敏感信息的泄露和减少不必要的前端暴露,建议将敏感数据与前端和管理端隔离。通用的题目信息可以存储在共享的数据库表中,可供前端使用;而每个题型的评测信息(如测试点、远端评测平台信息等)则存储在各自的独立数据库表或服务中,不直接暴露给前端。 -### API 回调 +## 行为 -如果评测的 `Token` 字段不为空,评测机需要将评测结果回调给调用方。回调的 `Endpoint` 需要从 `Redis` 中获取,`Key` 为 `gateway:{UUID}`。 +用户端向评测中间件所有的消息传递都是通过消息队列完成的。评测中间件需要监听消息队列,接收评测请求,进行评测,然后将评测结果按照 `sastoj` 的格式持久化,如果有回调需求,进行回调。 -## 支持的评测机 +### 初始化 -- [`go-judge`](https://github.com/criyle/go-judge):快速,简单,安全。 +所有题型的提交默认 `Channel` 为 `submission`,中间件在初始化时,需要将自己的所处理的题目类型存入 `problem_type` 表中: + +- `name_slug` 为题型内部名称(用于管理端) +- `display_name` 为题型名称(用于用户端) +- `channel_name` 为这种题型所使用的消息队列的 `Channel` 的名字 + +同时,需要将信息缓存到 `Redis` 中,`Key` 为 `problemType:{name_slug}`,`Value` 为 `Channel` 的名字。 + +### 接受评测请求 + +评测机需要监听对应的 `Channel`,并获取评测请求,评测请求的格式为 [`submission`](https://github.com/NJUPT-SAST/sastoj/blob/main/pkg/mq/submission.go)。 + +### 评测 + +评测逻辑由中间件自行实现,sastoj 不对评测过程做任何要求。 + +#### 本地评测 + +sastoj 现已支持 [`go-judge`](https://github.com/criyle/go-judge) 作为本地评测的评测机。 + +#### 测试点 + +sastoj 提供了测试点的支持,测试点文件存放在 `data/cases` 目录下,按照 `problem_id` 分别独立存放。 + +目前 `sastoj` 的测试点格式为 [rsjudge-test-cases-schema](https://github.com/Jisu-Woniu/rsjudge-test-cases-schema/tree/main/out) 中定义的 `toml` 格式。 + +### 结果返回 + +评测机需要将评测结果存入缓存和数据库。数据库连接可以使用 `ent` 也可以使用其他 `ORM` 框架。详见 [提交和自测](./submission-%26-self-test)。 diff --git a/docs/dev-guide/backend/judge-mid/submission-&-self-test.md b/docs/dev-guide/backend/judge-mid/submission-&-self-test.md index 541d2d5..39df57f 100644 --- a/docs/dev-guide/backend/judge-mid/submission-&-self-test.md +++ b/docs/dev-guide/backend/judge-mid/submission-&-self-test.md @@ -42,16 +42,6 @@ deactivate User 当网关或者 `user/contest` 接收到提交请求后,会生成一个临时的 `UUID`,作为这个提交的标识符。这个 `UUID` 会进入缓存,但是不会存入数据库。当评测中间价从**消息队列**中获得到提交后,会将其存入数据库,并在 Redis 中存入 `UUID -> submission`。注意,这个时候 `submission` 已经获取到了 `submission_id`。此外,`callback` 函数中,也需要用到 UUID 作为标识符。 -## Redis 存储 - -我们会将提交存入 Redis,以便于 `user` 端可以通过 `UUID` 获取到提交结果。 - -Key: `submission:{userID}:{UUID}` - -Value: `{submission}` - -其中 `submission` 是 `pkg/mq` 中定义的 `Submission` 结构体。当然,这个结构体会被序列化为 JSON 字符串,所以开发者也可以定义自己的结构体,只需要能够兼容原结构体即可,当然,直接使用原结构体会是更好的选择。 - ## 提交状态 SASTOJ 的评测状态有以下几种: @@ -72,3 +62,43 @@ SASTOJ 的评测状态有以下几种: |11|系统错误| 当评测提交后,状态将被设置为 `9`,评测中间件收到提交后,会将状态设置为 `10`,表示评测中。当评测完成后,会将状态设置为对应的状态。 + +## 持久化 + +在数据库中,评测结束后,需要将评测结果存入 `submissions` 表中,需要存入的字段有: + +- `problem_id`:外键,指向 `problems` 表 +- `user_id`:外键,指向 `users` 表 +- `code`:用户提交的代码(或者用户提交的内容) +- [`status`](#提交状态) +- `point`:本次评测的得分 +- `create_time`:提交时间,从消息队列中获取 +- `total_time`:总耗时 +- `max_memory`:最大内存 +- `language`:使用的语言 +- `case_version`:本次评测使用的测试点版本,从 `problems` 表中获取,用于判断测试点是否更新 +- `stderr`:编译错误或者运行错误的信息 + +对于测试点的结果,需要存入 `submission_cases` 表中,需要存入的字段有: + +- `submission_id`:外键,指向 `submissions` 表 +- `problem_case_id`:外键,指向 `problem_cases` 表 +- `state`:测试点的状态,同上 +- `time`:耗时 +- `memory`:内存使用 +- `message`:评测机返回的需要持久化的信息 +- `point`:本测试点的得分 + +## Redis 存储 + +我们会将提交存入 Redis,以便于 `user` 端可以通过 `UUID` 获取到提交结果。 + +Key: `submission:{userID}:{UUID}` + +Value: `{submission}` + +其中 `submission` 是 `pkg/mq` 中定义的 `Submission` 结构体。当然,这个结构体会被序列化为 JSON 字符串,所以开发者也可以定义自己的结构体,只需要能够兼容原结构体即可,当然,直接使用原结构体会是更好的选择。 + +## 更新回调 + +如果评测的 `Token` 字段不为空,评测机需要将评测结果回调给调用方。回调的 `Endpoint` 需要从 `Redis` 中获取,`Key` 为 `gateway:{UUID}`。再分别通过调用 `UpdateSubmission` 或 `UpdateSelfTest` 接口,将评测结果回调给调用方。