# 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=" \ "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": "", "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": "", "title": "CSP-J 基础", "tag": ["循环", "数组"], "attend": 32 }, "sections": [ { "id": "", "title": "循环结构", "pin": 1, "type": "normal", "homeworks": { "tid1": { "id": "", "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": "", "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=", "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);每次发布以当次登录态验收结果为准。