Files

375 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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);每次发布以当次登录态验收结果为准。