一行命令安装,认证一次,之后你只需要说人话:「明天有什么课」「我绩点多少」「下午第 5-6 节哪间教室空着」。
先看你最终会得到什么
装好之后,你的 AI(WorkBuddy / Claude / CodeBuddy 等)就多了一个校园助手技能。对话长这样:
plaintext
你:明天课表给我
AI:明天 10月27日 周二,第 7 周,3 门课——
1-2 节 人工智能数学基础 张 基102
3-5 节 JAVA语言系统设计 王 基311
6-7 节 WEB应用开发技术 雷 基316
plaintext
你:下午 5-6 节有空教室吗?想自习
AI:本部基础楼 3 层有 9 间,第二教学楼 205 也空着,容量 74–163 人。
plaintext
你:这学期绩点多少
AI:加权绩点 X.XX,已修 YY 学分(附逐科成绩)。
不需要打开浏览器,不需要记住教务系统在哪个菜单层级,也不用截图发给别人查。
为什么值得做这件事
教务系统不是给人日常用的,是给教务处存档用的。查一次课表的真实成本是:打开浏览器 → 进 jw.stdu.edu.cn → 过 WebVPN → 过统一认证(密码 + 图形验证码)→ 在折叠菜单树里找到「学生课表查询」→ 选学年学期 → 点查询 → 在密密麻麻的表格里找今天那几行。
而且这里有个容易被忽略的细节:教务的课表是按教学周次排的,不是按星期。单双周、分周次换教室、临时调课,都藏在「周次」里。所以你就算把整学期课表导出来,也不能简单地"看今天是周二就筛周二"——那样会错。
这个项目做的事,就是把这套流程压缩成一句话。
30 秒上手
bash
npx -y github:974294341-bit/stdu-cli install
这一条命令会做四件事:把技能复制到你的 AI 技能目录、装好 Playwright 的 Chromium 内核、写好本机配置、自动弹出学校统一身份认证页。你输学号、密码、图形验证码,登录一次,就结束了。之后直接在对话框里问就行。想只装技能稍后登录,加 --no-login。
源码仓库:github.com/974294341-bit/stdu-cli
它能干什么
你说 实际执行 说明
这学期课表 stdu-cli course 默认当前学年学期
明天课表 / 后天有课吗 stdu-cli course --tomorrow 自动换算教学周次后过滤
10 月 28 日上什么 stdu-cli course --date 2026-10-28 任意日期
成绩 / GPA stdu-cli grade 含加权绩点与学分统计
哪间教室没课 stdu-cli room --period 5-6 可按星期、节次、周次、校区过滤
换个号 stdu-cli relogin 只在换绑时需要
所有命令输出统一 JSON:{ ok, data, meta } 或 { ok:false, error:{code, message, hint} },方便 AI 解析,也方便你自己接到别的地方去。
它是怎么工作的:三层访问链
plaintext
jw.stdu.edu.cn/*
└─302─▶ webproxy.stdu.edu.cn/wengine-auth/login?id=311 WebVPN 网关
└─302─▶ cas.stdu.edu.cn/cas/login 统一认证(RSA 密码 + 图形验证码)
└─成功─▶ 回跳正方 V9 教务首页
教务本身是正方 ZFsoft V9(新版 /xtgl/ 命名空间)。因为它有图形验证码,程序化自动登录既做不到也不该做——所以工具用的是另一条路:用你自己的真实浏览器登录,只复用登录后的会话,不碰你的账号密码。
真实数据接口(不是猜的)
V9 有个很容易踩的坑:菜单点进去的功能页只是个 HTML 外壳,真实数据是页面加载后由驱动脚本另外 POST 拿的。所以"打开课表页面然后抓 HTML"是拿不到课的。我从驱动 JS 里确认了真实接口:
功能 菜单码 真实数据接口(POST) 返回字段
课表 N2151 /kbcx/xskbcx_cxXsgrkb.html kbList(+ sjkList 实践课)
成绩 N305005 /cjcx/cjcx_cxXsgrcj.html?doType=query items
空闲教室 N2155 /cdjy/cdjy_cxKxcdlb.html?doType=query items
几个有意思的细节:空教室的 zcd(周次)和 jcd(节次)是位掩码:zcd |= 1<<(周-1)、jcd |= 1<<(节次-1),表单字段必须全部提交,缺一个就返回 500 系统升级维护中;学期编码 xqm:3 = 秋,12 = 春,16 = 夏;当前教学周次不在课表接口里,藏在「按周次查询」页 N2154 的隐藏域 dqzc 和下拉框选中文本(形如 7(2026-10-26至2026-11-01))——这是实现"明天课表"的关键。
两个决定体验的设计
-
只认证一次。 CAS 下发的是会话级 cookie(expires=-1),浏览器一关就失效,这就是"每条命令都跳一次登录"的根因。做了两件事:① cookie 落盘,用 Playwright 的 storageState 导出到 ~/.stdu-cli/storage-state-
.json,下次启动注入回去,浏览器完全关掉也能续上;② 常驻守护进程,后台持有已登录页面,命令通过本机回环端口复用它,30 分钟无人调用自动退出。实测:把所有浏览器关掉、把守护进程杀掉,再查课表——零次认证提示,直接返回数据。 -
速度。 取数走三级:缓存 → 守护进程 → 临时开浏览器直连。
场景 实测耗时
缓存命中 约 1 ms(课表 6h / 成绩 30min / 空教室 10min)
守护进程热查询 150–450 ms
冷启动(重开浏览器,仍免登录) 约 3 s
每次返回都带 data.via(cache/daemon/direct)和 data.elapsedMs,慢在哪一层一眼可见。想强制拿最新数据加 --no-cache。
安全边界
不收集、不存储账号密码。登录发生在你自己的浏览器里,工具只复用已登录的会话。
只读。不绕过验证码、不模拟登录、不写入任何教务数据。
所有数据只在本机处理,缓存文件在 ~/.stdu-cli/。
学校改版导致解析失败时,返回 PARSE_FAILED 而不是瞎编结果,并提示跑 stdu-cli dump 校准。
做成这样花了什么
代码不复杂(约 2800 行 JS,25 个模块),难的是把"应该是什么样"变成"确认是什么样"。我最初的版本里,端点全是照着其他学校同类项目猜的"经验值",跑起来看着像那么回事,实际一条课都查不出来。真正让它可用的是之后那 8 轮真实会话校准,以及几次对着真实返回修解析 bug 的过程(比如"单双周"的作用域、空教室返回的字段名是 items 不是 rows)。这部分写在第二篇里。
已知边界与下一步
考试安排接口还没校准;校历、培养方案、选课相关功能未接入;手机端需要先配好 Node 环境。如果你也在石家庄铁道大学,欢迎直接 install 试一下,发现问题提 issue,或者跑 stdu-cli dump 把真实响应发我。License: MIT。
浙公网安备 33010602011771号