15 KiB
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。
浏览器调用示例:
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();
命令行调试示例:
curl -H "Accept: application/json" \
-H "Cookie: sid=<authorized-session-cookie>" \
"https://oj.example.com/d/example/course/api/list"
2.1 老师快速使用
老师通常按下面的顺序取数:
- 浏览器登录 OJ 后访问
/course/api/list,取得课程id。 - 访问
/course/api/detail/:cid,取得章节id和关联作业结构。 - 查看班级整体情况时调用反馈
overview;筛选学生时调用反馈students;需要逐题证据时再调用单生详情。 - 生成一周学习报告时先调用
/course/api/v1/weekly-report/students?q=...找到学生,再按返回的uid调用周报详情。
课程页面上的进度摘要、章节 HTML 和人员选择属于内部展示接口。外部报表优先使用反馈与周报 API,避免依赖 HTML 字段。
仓库提供只读验收脚本,密码通过环境变量传入,结果只输出 HTTP、响应结构、耗时与并发错误数:
COURSE_API_USERNAME="teacher" COURSE_API_PASSWORD="..." \
node scripts/verify-course-api-live.mjs \
--base-url="https://oj.example.com" \
--concurrency=30
3. 课程列表
GET /course/api/list?page=1&q=CSP
| 参数 | 默认值 | 说明 |
|---|---|---|
page |
1 |
正整数页码;每页数量读取系统设置 pagination.course |
q |
空 | 按课程标题搜索;少于 2 个字符时按标题前缀匹配 |
普通用户只会看到自己拥有、班级被分配或未限制班级的课程;原生教师按教师权限查看。
{
"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. 课程详情
GET /course/api/detail/:cid
用于读取课程基本信息、章节顺序和每个章节关联的三类作业。章节中的 homeworks 按 tid1、tid2、tid3 组织;考试章节通常只使用 tid1。
{
"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 课程进度摘要
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 单章节展示
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 课程人员分页
GET /course/api/v1/courses/:cid/students?group=班级组名&page=1&q=学生名
| 参数 | 默认值 | 说明 |
|---|---|---|
group |
空 | 当前账号可查看的小组;课程页面要求老师先选组再请求 |
page |
1 |
正整数页码,每页固定 50 人 |
q |
空 | 在所选组内按用户名、显示名或 UID 搜索,最长 100 字符 |
返回 students[]、page、pageSize 和 hasMore。课程页面滚动到底时才请求下一页,切换小组会清空原姓名搜索。
6. 章节成绩分析(敏感兼容接口)
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、代码和测试点均属于敏感教学数据。
{
"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
基础路径:
/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时不得表述为完整全班统计。
{
"generatedAt": "2026-08-25T00:00:00.000Z",
"enrollmentLimit": 1000,
"enrollmentTruncated": false,
"analyzedStudentCount": 32,
"rateUnit": "percent",
"thresholds": {
"lowFirstAttemptRate": 60,
"recoveryGain": 30,
"repeatedUnsolvedAttempts": 3
}
}
7.2 章节反馈概览
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 学生反馈列表
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 单个学生反馈详情
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,只能作为教师复核线索,不能直接当作对学生的自动评价。
推荐调用顺序:
- 请求
overview,确认统计范围、人数与enrollmentTruncated。 - 请求
students?signal=...筛出需要关注或明显进步的学生。 - 只对需要写反馈的学生请求单人详情。
- AI 根据事实起草文字;最终反馈必须由教师审核。
8. 学生周报 API v1
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。
9. 首刷开始(页面内部写接口)
POST /course/:cid/s/:csid/practice/:tid/:pid/start
请求体为空。成功响应:
{
"ok": true,
"problemUrl": "/p/1001?tid=<tid>",
"state": {}
}
业务拒绝通常仍返回 HTTP 200:
{
"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;每次发布以当次登录态验收结果为准。