外部作业源¶
最后更新:2026-09-22 22:57
清华大学的课程作业分布在多个系统中:网络学堂仅覆盖其中一部分,雨课堂承载课堂练习与 试卷,TUOJ 承载编程作业(AI 版与经典版两个实例),Tyche 承载部分院系的作业,DSA OJ 承载数据结构课的 OJ 作业。
外部作业源功能将上述系统的作业聚合到应用的作业页面,提供统一的截止时间展示、提交
状态判定与批改结果。插件通过 onethu.exthw.snapshot() 与 onethu.exthw.refresh()
使用该功能(见 api-reference.md §4)。
本文档说明各源的接入与凭据维护方式、故障恢复机制,以及新增作业源的实现步骤。
1. 数据模型¶
所有源统一映射为 ExternalHomework:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 源内唯一标识,用于列表去重 |
source |
string | 源标识,见 §2 |
courseName / title |
string | 课程名与作业标题 |
deadline |
string | 截止时间,格式 "YYYY-MM-DD HH:MM" |
kind |
"homework" | "exam" |
作业或试卷 |
url |
string? | 详情链接(学生端页面) |
submitted |
boolean | 提交状态 |
submittedCount / totalCount |
number? | 已提交题数 / 总题数(仅雨课堂有精确数据) |
graded |
boolean? | 批改状态,仅雨课堂可判定 |
audited |
boolean? | 是否旁听课堂(雨课堂 role===6) |
score / totalScore |
number? | 得分与卷面满分,仅在已提交且已出分/已批改时设置 |
leafTypeId / classroomId |
string? | 雨课堂整卷明细参数,供原生详情页拉取 |
2. 源与登录方式¶
| 源 | 标识 | 登录方式 | 恢复机制 |
|---|---|---|---|
| 雨课堂 | yuketang |
扫码登录(长轮询);官方网页登录(应用内 WebView 读取 Cookie)。短信通道已停用,官方已增加图形验证码校验 | 传输层中断按未扫码处理,沿用同一令牌继续轮询;移动端由前台服务保活 |
| TUOJ(AI 版) | tuoj |
复用清华统一认证会话漫游;可选配置 TUOJ 独立账密 | 接口返回 401 / 403 时自动重新漫游一次 |
| TUOJ(经典版) | tuojClassic |
同上 | 同上 |
| Tyche | tyche |
复用清华统一认证会话漫游 | 同 TUOJ |
| DSA OJ | dsa |
邮箱 + 密码登录(站点无统一认证,仅手动账密);亦可手动填写会话 Cookie | 会话失效抛 DsaSessionError,由设置页引导重新登录;不自动重漫游 |
3. 状态判定¶
提交状态:由各源独立查询得出。查询失败或无法判定时(例如雨课堂试卷类条目缺少
访问权限)统一判定为未提交,避免出现「已提交」的误报。DSA OJ 属此类:其
assignment.status 字段语义尚未确认(R15 20.1 实测未定),故保守判定为未提交,
待真机联调后补充判定。
批改状态:
- 雨课堂:读取
get_exercise_list返回的status字段(取值 4 与 3 表示已批改), 并结合分值占位符判定;作业与试卷的详情链接指向学生端页面/ai-workspace/lms-graph/...。 - Tyche:读取
task/Status的submissionList[](含pid、score、result、submitedTime),按pid取最新一次提交汇总。所有题目均有有效得分时判定为已 批改,score为各题得分之和。 - DSA OJ:不提供批改结果——课程详情接口的
assignmentList[]只有标题与截止时间, 无得分字段,故graded与score恒为空。
3.1 雨课堂原生详情与提交入口(R20–R21)¶
雨课堂的作业正文是加密字体 + LaTeX + 外链图片的组合,官方页面在应用内直接打开时字体与公式缺失、 图片 401。R20 起改为原生详情页:正文在应用内渲染,不跳转至浏览器。
| 能力 | 实现要点 |
|---|---|
| 题干渲染 | 正文进入本地 srcdoc 沙箱 iframe(无网络权限、脚本白名单);加密字体取自响应中的 data.font(exam_font_<hash>.ttf),下载后以 @font-face 应用,缓存 7 天、失败 10 分钟内不重试 |
| 公式 | KaTeX 随包内置(vendor/katex,懒加载),$…$ 与 $$…$$ 本地渲染,正文不外传 |
| 图片 | 经应用侧带会话 Cookie 代理取回后内联(data:),避免 iframe 内出现 401 |
| 降级 | 字体/公式/图片任一环节失败都逐级降级(纯文本 → 提示条),任何情况下均不出现白屏 |
| 分数与评语 | 已批改作业在入口处直接显示 X/Y(与考试同一显示位);详情页按题给出得分、我的作答与老师评语(总评与具名批注同文时去重,避免同一条评语渲染两次) |
| 提交入口(R20-C1,第一阶段) | 内嵌官方作答页(WebView)+ 注入会话 Cookie,资格判定为纯函数 |
| 主观题原生作答(R20-C2,第二阶段) | 主观题在应用内撰写与提交(工具栏对齐官方、插图四通道含拍照、KaTeX 公式、草稿与剩余次数),经用户确认对话框后调用官方提交接口;方案与实测见 外部作业源-需求与实现方案.md §31,功能说明见 homework.md §4 |
| 会话保活(R21-B) | 会话健康检查 + 保活 + Cookie 导出/导入,降低“打开时会话已失效”的概率;Tyche 侧登录失效静默自动重登(R21-A) |
提交边界:外部作业源的展示层为只读——只读取标题、课程、截止时间、提交与批改状态 以及题干正文,用于在应用内呈现;试卷不显示任何提交入口。作业的作答与提交能力仅限用户 亲笔:应用只承担内容排版渲染、图片上传与提交动作,不做 AI 代写、代交、自动提交或批量 提交;边界清单与工程护栏见 homework.md §5。
4. 会话失效恢复¶
TUOJ 系源在接口返回 401 或 403 时自动重新漫游一次并重新拉取数据。此前的自动漫游仅 在源未配置时触发,已配置但凭据失效的情况缺少恢复手段。
| 约束 | 取值 |
|---|---|
| 重试间隔 | 同一源两次自动重试间隔不小于 10 分钟 |
| 重试上限 | 每进程每源不超过 3 次 |
| 并发处理 | 同一源的并发 401 共享同一请求 |
| 退出抑制 | 用户显式退出登录后不自动重登 |
| 失败提示 | 自动重登仍失败时,作业页显示提示条并附设置页重登入口 |
过夜老化与启动续期(R23):TUOJ 会话过夜老化后,接口可能返回 HTTP 200 但内容是登录页
HTML(非 JSON),该响应同样判定为会话失效。此外每次启动 OneTHU 会对已配置的 TUOJ CAS 源
主动续期一次(标记 tuojStartupRenewed):走强制刷新并放宽 24 小时频控,仍尊重用户的显式
退出抑制。护栏 tools/tuoj-r23-test.mjs。
DSA OJ 不接入清华统一认证,没有可用的自动恢复通道:会话失效时(user.php
checklogin 返回未登录、业务 error≠0、或响应非 JSON)抛出 DsaSessionError,
由作业页错误提示与设置页重登入口引导用户重新输入邮箱与密码。
5. 新增作业源¶
- 实现数据获取:在
packages/core/src/exthw/下新增源实现文件,返回ExternalHomework[]。提交与批改状态需真实查询得出,无法判定时判定为未提交。 类型定义加入types.ts并从index.ts导出。 - 注册源标识:扩展
ExtHwSourceId联合类型,在extHwSourceName()中补充显示 名称,并在设置页按平台归组。 - 处理凭据:需要独立凭据的源使用
ExtHwCreds结构并在设置页提供表单;复用清华 统一认证的源不需要独立凭据。无统一认证的站点(如 DSA OJ)在login.ts中实现 账密登录工具函数,登录成功后拼接会话 Cookie 并交回设置页保存。 - 补充测试:在
tools/exthw-status-test.mjs中增加断言(使用模拟数据,不依赖 真实网络);具备登录态的源可在tools/exthw-smoke.mjs中增加真实数据验证。 - 更新文档:在本文档 §2 表格中新增一行,源特有的登录与恢复行为补充至 §3、§4。
6. 界面与测试¶
设置页:设置 → 外部作业源,按平台归组。移动端扫码面板为全屏显示,并提示使用 另一台设备扫码、保持应用在前台。DSA OJ 位于「OJ 平台」分组(默认折叠),提供邮箱 + 密码登录,并保留手动填写会话 Cookie 的高级入口。
测试工具:
| 工具 | 覆盖范围 |
|---|---|
tools/exthw-status-test.mjs |
聚合状态机、状态判定、频控逻辑;DSA OJ 的 endDate 容错解析与会话失效判定 |
tools/tuoj-cas-test.mjs |
CAS 漫游、二次认证提示、重新漫游 |
tools/tuoj-r23-test.mjs |
TUOJ 过夜老化判定(200 + 非 JSON)与启动续期 |
tools/ykt-qr-test.mjs |
扫码状态机、保活服务生命周期、传输层超时 |
tools/ykt-body-test.mjs |
题干正文渲染(加密字体栈合法性、LaTeX 与图片处理、降级链) |
tools/ykt-detail-ui-test.mjs |
详情页纯函数:分数文案、评语去重、作答态映射 |
tools/ykt-submit-test.mjs |
主观题提交:签名、multipart 字段顺序、callback 校验 |
tools/c2-redline-test.mjs |
学术红线护栏(提交能力不进插件工具清单、无绕过用户确认的路径) |
tools/ykt-exercise-detail-test.mjs |
get_exercise_list → 详情模型映射(含组作业与缺分边界) |
tools/ykt-detail-smoke.mjs / tools/ykt-session-smoke.mjs |
真实凭据下的详情拉取与会话保活冒烟(环境变量同上一行) |
tools/exthw-smoke.mjs |
真实凭据下的端到端验证(环境变量 YKT_COOKIE、TUOJ_COOKIE、TUOJ_CLASSIC_COOKIE、TYCHE_COOKIE、DSA_COOKIE) |
7. 接入记录¶
各源的接入批次、实测结论与已知限制。批次号与源码注释中的编号一致,便于回溯到具体 实现与实测记录。
| 源 | 引入批次 | 实测结论与已知限制 |
|---|---|---|
| 雨课堂 | 初始版本 | 扫码登录(长轮询)与官方网页登录(应用内 WebView 读 Cookie)两条通道;短信通道已停用;试卷提交状态经 /v/exam/cover 取得;作业与试卷详情链接指向学生端 /ai-workspace/lms-graph/... |
| TUOJ(AI 版) | 初始版本 | 统一认证漫游为默认通道,另可配置独立账密直连;返回 401 / 403 触发一次自动重漫游,设备信任与二次认证均有可操作引导 |
| Tyche | 初始版本 | 复用统一认证漫游;批改结果取 task/Status 的 submissionList[],按 pid 汇总最新一次提交 |
| TUOJ(经典版) | R15 20.1 | 站点 oj.cs.tsinghua.edu.cn 与 AI 版接口行为一致,复用同一套客户端实现,仅 base URL 与漫游回调不同 |
| 雨课堂(原生详情) | R20-A / B2 / B3 | 原生只读详情页(题干加密字体、KaTeX 公式、图片代理、srcdoc 沙箱,任何情况下均不出现白屏);已批改作业入口显示 X/Y,评语按题结构化呈现;加密字体根因是 font-family 中混入 CSS 全局关键字 inherit 导致整条声明被丢弃 |
| 雨课堂(提交入口) | R20-C1 | 内嵌官方作答页 + 注入会话 Cookie,资格为纯函数;试卷不显示提交入口 |
| 雨课堂(主观题原生作答) | R20-C2 P2 / P3 | 逐题原生作答编辑器(工具栏对齐官方、插图四通道含拍照上传、KaTeX 公式、草稿与剩余次数);提交经用户确认后调用官方接口,成功后重新拉取真实状态;late_submission 存在对象与数字两种形态,按双口径容错解析 |
| Tyche(静默重登) | R21-A | 登录失效时按 R19 的 TUOJ 会话失效模式静默重漫游一次;图形验证码场景给可操作引导 |
| 雨课堂(会话保活) | R21-B | 会话健康检查 / 保活 / Cookie 导出导入;保活有效性实验协议待回填 |
| DSA OJ | R15 20.1 / 20.2 | 站点 dsa.cs.tsinghua.edu.cn/oj/,老式 Bootstrap/jQuery 站点,接口均为 POST form-urlencoded,会话依赖 Cookie;登录为邮箱 + 密码,无统一认证;截止时间取 assignment.endDate,其格式不确定(站点标注 UTC+8),故按多格式容错解析;提交状态保守判定为未提交(status 语义待联调确认);不提供批改结果;只读——仅拉取标题、课程与截止时间,不提交、不抓取题目正文 |