375 lines
15 KiB
Markdown
375 lines
15 KiB
Markdown
# HydroOJ 课程插件 API 使用文档
|
||
|
||
本文档覆盖 课程插件(hydrooj-course) 当前源码注册的 JSON API,包括兼容查询、课程页面内部展示、教学反馈、学生周报和首刷写入接口。
|
||
|
||
API 只输出课程与学习事实,不自动生成评价或发送消息。面向家长或学生的文字必须由上层工具生成并由教师审核。
|
||
|
||
## 1. 接口总览
|
||
|
||
| 方法 | 路径 | 用途 | 主要权限 | 稳定性 |
|
||
| --- | --- | --- | --- | --- |
|
||
| `GET` | `/course/api/list` | 查询当前账号可见课程 | 已登录用户 | 兼容接口 |
|
||
| `GET` | `/course/api/detail/:cid` | 查询课程与章节结构 | 可访问该课程 | 兼容接口 |
|
||
| `GET` | `/course/api/analysis/:cid/:csid` | 查询章节成绩、首刷和最终结果 | 教师或授权校区查看者 | 敏感兼容接口 |
|
||
| `GET` | `/course/api/v1/courses/:cid/summary` | 查询当前查看学生的课程进度摘要 | 可访问该课程 | 页面内部接口 |
|
||
| `GET` | `/course/api/v1/courses/:cid/sections/:csid/view` | 按需查询一个章节的授权展示数据 | 可访问该课程 | 页面内部接口 |
|
||
| `GET` | `/course/api/v1/courses/:cid/students` | 分页搜索授权范围学生 | 教师或授权校区查看者 | 页面内部接口 |
|
||
| `GET` | `/course/api/v1/courses/:cid/sections/:csid/feedback/overview` | 章节概览与逐题统计 | 教师或授权校区查看者 | v1 |
|
||
| `GET` | `/course/api/v1/courses/:cid/sections/:csid/feedback/students` | 分页筛选学生反馈事实 | 教师或授权校区查看者 | v1 |
|
||
| `GET` | `/course/api/v1/courses/:cid/sections/:csid/feedback/students/:uid` | 单生逐题反馈证据 | 教师或授权校区查看者 | v1 |
|
||
| `GET` | `/course/api/v1/weekly-report/students` | 搜索授权范围内的周报学生 | 教师或授权校区查看者 | v1 |
|
||
| `GET` | `/course/api/v1/weekly-report/students/:uid` | 查询单生一周课程与提交事实 | 教师或授权校区查看者 | v1 |
|
||
| `POST` | `/course/:cid/s/:csid/practice/:tid/:pid/start` | 开启或重启首刷计时 | 已登录且满足课程规则 | 页面内部接口 |
|
||
|
||
页面内部展示接口只返回授权后的摘要、章节 HTML 和公开用户渲染字段,均设置 `Cache-Control: private, no-store`。
|
||
|
||
## 2. 认证、域路径与安全边界
|
||
|
||
- 所有接口使用当前 HydroOJ 登录会话,不接受通用管理员 Token。
|
||
- 主域直接使用表格中的路径;非主域在前面加 `/d/:domainId`。
|
||
- 调用时使用 `Accept: application/json`,跨页面请求保留同源 Cookie。
|
||
- 不要把真实 Cookie、账号密码、学生姓名、代码或响应原文写入仓库、聊天记录和公开日志。
|
||
- `analysis` 可能返回代码、编译错误、测试点和提交历史,只能在受控教师工具中按需开启。
|
||
- 教学反馈 v1 不返回邮箱、手机号、IP、源代码、完整提交历史、编译器原文或测试点详情,并设置 `Cache-Control: private, no-store` 与 `X-Content-Type-Options: nosniff`。
|
||
|
||
浏览器调用示例:
|
||
|
||
```js
|
||
const response = await fetch('/course/api/list', {
|
||
credentials: 'same-origin',
|
||
headers: { Accept: 'application/json' },
|
||
});
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
```
|
||
|
||
命令行调试示例:
|
||
|
||
```bash
|
||
curl -H "Accept: application/json" \
|
||
-H "Cookie: sid=<authorized-session-cookie>" \
|
||
"https://oj.example.com/d/example/course/api/list"
|
||
```
|
||
|
||
### 2.1 老师快速使用
|
||
|
||
老师通常按下面的顺序取数:
|
||
|
||
1. 浏览器登录 OJ 后访问 `/course/api/list`,取得课程 `id`。
|
||
2. 访问 `/course/api/detail/:cid`,取得章节 `id` 和关联作业结构。
|
||
3. 查看班级整体情况时调用反馈 `overview`;筛选学生时调用反馈 `students`;需要逐题证据时再调用单生详情。
|
||
4. 生成一周学习报告时先调用 `/course/api/v1/weekly-report/students?q=...` 找到学生,再按返回的 `uid` 调用周报详情。
|
||
|
||
课程页面上的进度摘要、章节 HTML 和人员选择属于内部展示接口。外部报表优先使用反馈与周报 API,避免依赖 HTML 字段。
|
||
|
||
仓库提供只读验收脚本,密码通过环境变量传入,结果只输出 HTTP、响应结构、耗时与并发错误数:
|
||
|
||
```bash
|
||
COURSE_API_USERNAME="teacher" COURSE_API_PASSWORD="..." \
|
||
node scripts/verify-course-api-live.mjs \
|
||
--base-url="https://oj.example.com" \
|
||
--concurrency=30
|
||
```
|
||
|
||
## 3. 课程列表
|
||
|
||
```http
|
||
GET /course/api/list?page=1&q=CSP
|
||
```
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `page` | `1` | 正整数页码;每页数量读取系统设置 `pagination.course` |
|
||
| `q` | 空 | 按课程标题搜索;少于 2 个字符时按标题前缀匹配 |
|
||
|
||
普通用户只会看到自己拥有、班级被分配或未限制班级的课程;原生教师按教师权限查看。
|
||
|
||
```json
|
||
{
|
||
"courses": [
|
||
{
|
||
"id": "<cid>",
|
||
"title": "CSP-J 基础",
|
||
"tag": ["循环", "数组"],
|
||
"group": "CSP-J",
|
||
"cover": "/file/course-cover.webp",
|
||
"attend": 32,
|
||
"sectionCount": 12,
|
||
"beginAt": "2026-08-01T00:00:00.000Z",
|
||
"penaltySince": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 4. 课程详情
|
||
|
||
```http
|
||
GET /course/api/detail/:cid
|
||
```
|
||
|
||
用于读取课程基本信息、章节顺序和每个章节关联的三类作业。章节中的 `homeworks` 按 `tid1`、`tid2`、`tid3` 组织;考试章节通常只使用 `tid1`。
|
||
|
||
```json
|
||
{
|
||
"course": {
|
||
"id": "<cid>",
|
||
"title": "CSP-J 基础",
|
||
"tag": ["循环", "数组"],
|
||
"attend": 32
|
||
},
|
||
"sections": [
|
||
{
|
||
"id": "<csid>",
|
||
"title": "循环结构",
|
||
"pin": 1,
|
||
"type": "normal",
|
||
"homeworks": {
|
||
"tid1": {
|
||
"id": "<tid>",
|
||
"title": "📝 课堂练习",
|
||
"problemCount": 6
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
已删除、缺失或当前账号无权读取的题目不会计入 `problemCount`。
|
||
|
||
## 5. 页面轻量展示 API
|
||
|
||
这三条接口供课程详情页局部更新使用,均返回 `Cache-Control: private, no-store`。浏览器端只在当前页面会话中使用 15~60 秒的内存缓存,权限判断不会跨请求缓存。
|
||
|
||
### 5.1 课程进度摘要
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/summary
|
||
GET /course/api/v1/courses/:cid/summary?uid=123
|
||
```
|
||
|
||
`uid` 省略时查询自己。教师只能查询授权范围内学生。响应中的 `sections[]` 包含 `csid`、`total`、`done`、`visible` 和 `percent`;成绩未公开时 `done`、`percent` 为 `null`。
|
||
|
||
### 5.2 单章节展示
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/sections/:csid/view
|
||
GET /course/api/v1/courses/:cid/sections/:csid/view?uid=123
|
||
```
|
||
|
||
响应包含授权后的章节 `html`、首刷控件数据 `practice`、当前章节 `summary` 和是否存在待评测记录的 `pending`。本接口只读取当前章节及解锁所需前置条件。
|
||
|
||
### 5.3 课程人员分页
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/students?group=班级组名&page=1&q=学生名
|
||
```
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `group` | 空 | 当前账号可查看的小组;课程页面要求老师先选组再请求 |
|
||
| `page` | `1` | 正整数页码,每页固定 50 人 |
|
||
| `q` | 空 | 在所选组内按用户名、显示名或 UID 搜索,最长 100 字符 |
|
||
|
||
返回 `students[]`、`page`、`pageSize` 和 `hasMore`。课程页面滚动到底时才请求下一页,切换小组会清空原姓名搜索。
|
||
|
||
## 6. 章节成绩分析(敏感兼容接口)
|
||
|
||
```http
|
||
GET /course/api/analysis/:cid/:csid
|
||
GET /course/api/analysis/:cid/:csid?withCode=true
|
||
GET /course/api/analysis/:cid/:csid?withHistory=true&withCode=true
|
||
```
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `withCode` | `false` | 返回最终采用记录的代码、语言、编译错误和测试点摘要 |
|
||
| `withHistory` | `false` | 返回全部尝试历史;单次最多读取 5000 条记录 |
|
||
|
||
权限与限制:
|
||
|
||
- 原生教师和授权校区查看者可以读取授权范围内的成绩。
|
||
- 只有原生教师可以真正启用 `withCode`、`withHistory`;校区查看者传入后仍会被强制关闭。
|
||
- 单次最多读取 500 名报名学生。
|
||
- `historyTruncated=true` 表示提交历史达到 5000 条上限,调用方不得把响应描述为完整历史。
|
||
- 学生、班级、校区、记录 ID、代码和测试点均属于敏感教学数据。
|
||
|
||
```json
|
||
{
|
||
"course": { "title": "CSP-J 基础" },
|
||
"section": { "id": "<csid>", "title": "循环结构" },
|
||
"homeworks": [],
|
||
"students": [],
|
||
"campuses": [],
|
||
"campusGroups": [],
|
||
"historyTruncated": false,
|
||
"generatedAt": "2026-08-25T00:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
每名学生的 `homeworks.*.problems` 可能包含:
|
||
|
||
- `firstAttempt`:首刷用时、得分、满分、状态、记录 ID 和提交时间;
|
||
- 当前最终 `status`、`score`、`maxScore`、`scorePercent`、`submitCount`、`bestRid`;
|
||
- `withCode=true` 时的 `code`、`lang`、`error`、`testCases`;
|
||
- `withHistory=true` 时的 `history`。
|
||
|
||
新工具优先使用下面的教学反馈 v1。只有需要代码或完整提交诊断时才调用本接口。
|
||
|
||
## 7. 教学反馈 API v1
|
||
|
||
基础路径:
|
||
|
||
```text
|
||
/course/api/v1/courses/:cid/sections/:csid/feedback
|
||
```
|
||
|
||
### 7.1 通用约定
|
||
|
||
- `schemaVersion` 当前为 `1.0`;v1 内只增加可选字段,不改变已有字段含义。
|
||
- 比率以百分数返回,例如 `72.5` 表示 `72.5%`。
|
||
- 没有有效分母时比率为 `null`,不会把“没有数据”伪装成 `0`。
|
||
- 时间使用 ISO 8601,用时使用毫秒。
|
||
- 缺失或无权查看的题目不进入题数、完成率和反馈统计。
|
||
- 单次最多分析 1000 名授权范围内的报名学生。`meta.enrollmentTruncated=true` 时不得表述为完整全班统计。
|
||
|
||
```json
|
||
{
|
||
"generatedAt": "2026-08-25T00:00:00.000Z",
|
||
"enrollmentLimit": 1000,
|
||
"enrollmentTruncated": false,
|
||
"analyzedStudentCount": 32,
|
||
"rateUnit": "percent",
|
||
"thresholds": {
|
||
"lowFirstAttemptRate": 60,
|
||
"recoveryGain": 30,
|
||
"repeatedUnsolvedAttempts": 3
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 章节反馈概览
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/sections/:csid/feedback/overview
|
||
GET /course/api/v1/courses/:cid/sections/:csid/feedback/overview?group=班级组名
|
||
```
|
||
|
||
主要返回:
|
||
|
||
- `summary`:学生数、开做数、提交数、完成人数、待关注人数、完成率、首刷得分率、最终得分率、平均尝试次数和平均首刷用时;
|
||
- `homeworks[].problems[]`:逐题开做率、提交率、通过率、平均尝试次数、首刷覆盖率和得分率;
|
||
- `groups`、`campuses`:当前账号可筛选的范围,不返回成员 UID 列表。
|
||
|
||
`group` 只能使用当前账号可查看的课程班级组名。
|
||
|
||
### 7.3 学生反馈列表
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/sections/:csid/feedback/students
|
||
```
|
||
|
||
| 参数 | 默认值 | 限制 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `page` | `1` | 正整数 | 页码 |
|
||
| `limit` | `50` | 最大 `100` | 每页人数 |
|
||
| `group` | 空 | 当前账号授权组名 | 只统计该班级/组 |
|
||
| `signal` | 空 | 见标签表 | 只返回具有该标签的学生 |
|
||
| `q` | 空 | 最长 100 字符 | 按用户名或 UID 搜索 |
|
||
|
||
响应包含 `summary`、`homeworks`、`students`、`pagination`、`filters` 和 `meta`。列表中的 `homeworks` 只返回每类作业的题数与完成计数,不展开逐题证据。
|
||
|
||
### 7.4 单个学生反馈详情
|
||
|
||
```http
|
||
GET /course/api/v1/courses/:cid/sections/:csid/feedback/students/:uid
|
||
```
|
||
|
||
只有该学生已报名当前课程且位于调用者授权范围内时才返回。`student.homeworks.*.problems` 展开逐题首刷与当前最终结果,但仍不返回代码、完整历史或用户隐私字段。
|
||
|
||
若 Hydro 作业状态尚未异步写回但首刷快照已存在,`final.source` 为 `firstAttemptFallback`;正常读取作业状态时为 `contestStatus`。
|
||
|
||
### 7.5 确定性反馈标签
|
||
|
||
| code | 含义 | 判定口径 |
|
||
| --- | --- | --- |
|
||
| `NOT_STARTED` | 尚未开始 | 已布置题目但没有首刷或提交 |
|
||
| `INCOMPLETE` | 尚未完成 | 已开始但没有通过全部题目 |
|
||
| `LOW_FIRST_ATTEMPT` | 首刷掌握不足 | 有记录的首刷总得分率低于 60% |
|
||
| `REPEATED_UNSOLVED` | 反复尝试仍未通过 | 至少一题提交 3 次仍未通过 |
|
||
| `RECOVERED_AFTER_PRACTICE` | 订正后提升明显 | 同一批有首刷记录的题,最终得分率比首刷提高至少 30 个百分点 |
|
||
| `COMPLETED` | 已完成全部题目 | 所有有效题目均已通过 |
|
||
|
||
标签携带中文 `evidence`,只能作为教师复核线索,不能直接当作对学生的自动评价。
|
||
|
||
推荐调用顺序:
|
||
|
||
1. 请求 `overview`,确认统计范围、人数与 `enrollmentTruncated`。
|
||
2. 请求 `students?signal=...` 筛出需要关注或明显进步的学生。
|
||
3. 只对需要写反馈的学生请求单人详情。
|
||
4. AI 根据事实起草文字;最终反馈必须由教师审核。
|
||
|
||
## 8. 学生周报 API v1
|
||
|
||
```http
|
||
GET /course/api/v1/weekly-report/students?q=学生名&limit=20
|
||
GET /course/api/v1/weekly-report/students/:uid?start=2026-09-01&end=2026-09-08
|
||
```
|
||
|
||
搜索接口返回授权范围学生的 `uid`、名称、小组和可见课程数。详情接口返回指定自然周的课程、章节、作业、提交事实、首次提交通过率、课程首刷通过率和训练候选。
|
||
|
||
`start`、`end` 使用 `YYYY-MM-DD`,`end` 不包含在统计范围内,最长 31 天。`includeCode=true` 只允许原生教师,并会返回敏感源代码;普通数据访问不要开启。完整字段和统计口径见 [`API_WEEKLY_REPORT.md`](./API_WEEKLY_REPORT.md)。
|
||
|
||
## 9. 首刷开始(页面内部写接口)
|
||
|
||
```http
|
||
POST /course/:cid/s/:csid/practice/:tid/:pid/start
|
||
```
|
||
|
||
请求体为空。成功响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"problemUrl": "/p/1001?tid=<tid>",
|
||
"state": {}
|
||
}
|
||
```
|
||
|
||
业务拒绝通常仍返回 HTTP `200`:
|
||
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"message": "请先完成前一类练习。"
|
||
}
|
||
```
|
||
|
||
该接口会参加关联作业并写入首刷进度,不是通用第三方 API。调用方必须同时检查 HTTP 状态与 `ok`,且不得用 GET、预加载或批量探测触发它。
|
||
|
||
服务端依次校验:登录、课程可见性、学生报名、章节归属、普通 homework、题目归属、三槽解锁和作业时间。教师仍需满足本人前一槽完成条件,但不受学生报名和课程开放时间限制。
|
||
|
||
## 10. 错误与完整性判断
|
||
|
||
| HTTP 状态 | 常见原因 |
|
||
| --- | --- |
|
||
| `400` | 参数格式错误、未知反馈标签、查询文本过长 |
|
||
| `403` | 未登录、没有成绩权限、越权访问课程、班级或学生 |
|
||
| `404` | 路由未部署,或课程、章节不存在,或章节不属于课程 |
|
||
|
||
调用方必须检查:
|
||
|
||
- HTTP 状态码和 `Content-Type`;
|
||
- 首刷接口的 `ok`;
|
||
- 反馈 v1 的 `meta.enrollmentTruncated`;
|
||
- 旧 `analysis` 的 `historyTruncated`。
|
||
|
||
仅返回 JSON 不代表业务成功,也不能用登录跳转或静态路由检查代替真实授权账号验收。
|
||
|
||
## 11. 版本与现网核验
|
||
|
||
- 本文档以当前工作区源码路由为准。
|
||
- 某个部署环境是否包含全部接口,应通过登录后的真实 HTTP 请求逐项核验。
|
||
- 2026-08-25 的历史 qonnwolf 核验结果见 [`API_CHECK_REPORT_2026-08-25.md`](./API_CHECK_REPORT_2026-08-25.md);每次发布以当次登录态验收结果为准。
|