北京科技大学(USTB)课表 App:WebView 登录本研一体化教务系统一键导入课表与成绩,本地周视图展示,上课与考前提醒。
课表
- 一次扫码跑完三件事:内置 WebView 完成统一身份认证登录(App 不接触学号密码)后,同一次扫码自动导入学期课表 + 抓取成绩/考试/学业进度 + 登录云账号;课表抓完直接入库(保留隐藏状态与自定义课),末尾给出逐项结果与失败项重试。教务会话还在时连扫码都不需要——进入即静默跑完
- 官方教学周日历:导入时同步教务校历,教学周↔日期精确映射——国庆等长假周不占教学周序号(第 4 周 = 10/5,而非 9/28);假期中自动提示并定位到假期后第一个教学周
- 周视图课表:按"大节"排版(1-2 节 = 第一大节 … 第 11-12 节 = 第六大节,个别 13 节加课也归入第六大节),支持滑动切周、周次下拉跳转、单双周标注、隐藏周末开关、隐藏本周不上的课开关(关闭时这些课淡化显示)、同周冲突课程并排窄列、今天实心胶囊高亮、顶栏日期+学期状态(未开学/第 N 周/假期中)
- 课程管理:手动增删改课程(含多时段),教务导入数据与手动课程共存;三类课程(教务导入 / 自定义 / 示例)都可隐藏,在「我的」→ 隐藏的课程里恢复(同时停止其上课提醒);教务导入课只能隐藏不能删除,自定义课与示例课可直接删除;同名调课/单双周拆分行自动合并
- 深色模式:跟随系统 / 浅色 / 深色三态切换;浅色暖渐变 + 暗色暗渐变双主题背景
教务(无课教室 | 日程 | 成绩 | 考试)
- 无课教室:按楼栋查看今天各大节的空教室(教室名 + 座位数 + 空座率),教室名自然排序(1002 排在 503 之后)、当前大节自动展开、已结束的大节淡化并标"已结束"、顶部交代"今天几号 · 数据何时更新"、下拉刷新。数据来自贝壳教学平台(
ustb.smartclass.cn),无需登录教务账号,请求里不含学号/姓名 - 日程:个人待办日程,支持每天重复 / 每周固定几天 / 一次性三种模式;按日期分组展示(今天→明天→未来两周),今日已过事项淡化、每日打卡(次日自动复活);日期用日历选择,时间(时/分双列)与提前分钟(1–120)为 iOS 风格滚轮(拖动吸附 + 点选居中);到点前按各自提前量提醒
- 成绩:按学期分组的成绩单,单科排名(如 排名 5/128),点击弹详情(排名/考核方式/学分/开课学院等),不及格标红、学期组头"N 门未通过"
- 加权成绩:只看必修课的加权平均分,学年/学期双列筛选,可自定义排除课程
- GPA:本地 4.0 制计算(90-100=4.0 / 85-89=3.7 / 80-84=3.4 / 75-79=3.0 / 70-74=2.4 / 65-69=2.0 / 60-64=1.0,补考/重修覆盖正考)
- 学分修读进度:毕业总进度条(已修/要求)+ 17 类课程性质的要求 vs 已完成进度条,口径与教务网一致
- 成绩隐私:小眼睛一键隐藏/显示全部分数
- 考试安排:按日期分组、D-N 倒计时、座位号、考点展示
提醒
- 上课提醒:每门课程块开始前 N 分钟通知(默认 15 分钟可调),精确闹钟 + 开机自动重排
- 考前提醒:考试前一天 20:00 与开考前 1 小时各提醒一次(含地点与座位号)
- 日程提醒:每个日程事项按自定义提前量提醒(1–120 分钟),随事项增删改即时重排
我的
- 云同步(opt-in):与课表/成绩共用同一次扫码登录(账号 = 学号,无密码);服务端会拿本次登录的教务会话向教务系统复核学号(不是只认"学号+姓名",详见下文隐私一节)。启用后课表/自定义课程/日程/成绩/考试/学业进度与相关设置整包自动备份到云端(改动后 8 秒去抖上传)。换机/重装场景会自动识别:云端有备份而本机没有数据时直接恢复;两边都有数据时弹窗让你选"从云端恢复 / 用本机覆盖云端 / 暂不上传",绝不静默覆盖(恢复前强制校验快照签名),恢复后提醒自动重排
- 学籍信息卡(姓名/学号/学院/专业/班级),主题三态切换,检查更新,外部系统快捷入口(评教/大创),清除成绩缓存,联系开发者
当前版本 1.3.7(versionCode 48)。Releases 页面下载最新 APK(release 签名,可直接覆盖安装升级;v1.0.14 及更早版本因包名变更需卸载重装)。
App 内「我的 → 检查更新」走自有更新源(/api/bs/app/latest):响应带 Ed25519 签名、并覆盖安装包 SHA-256,验签不通过则整体忽略该源(不拿未验签的强更或下载链接),装包前再核对一次摘要。
- Kotlin + Jetpack Compose(Material 3)
- Room(课程 / 节次时间 / 成绩 / 考试 / 日程)+ DataStore(学期配置 / 教学周日历 / 提醒 / 主题 / 隐私开关)
- AlarmManager(上课 / 考试 / 日程提醒调度)
- OkHttp + kotlinx.serialization(云同步 REST,api.caeamer.com 的 /api/bs 模块;无 Retrofit)
- 云同步全链路 HTTPS:源站不暴露任何公网端口,由 Cloudflare Tunnel(cloudflared 主动出连)承接
api.caeamer.com;客户端不再有明文 HTTP 通道 - 教务适配层:WebView 注入 JS 复用登录会话,直接调教务结构化 JSON 接口(无需解析 HTML)
- AndroidKeyStore(AES-GCM)加密保存云 token;Ed25519 验签(自有更新源元数据 + 云备份快照)
- 云登录:教务统一认证 → SSO(
sso.ustb.edu.cn/ac-h5)扫码 → 微认证二维码(sis.ustb.edu.cn/connect/qrpage|qrimg|state)。App 用shouldInterceptRequest原生捕获二维码 sid 并自行长轮询,不依赖页面自带轮询(慢网下它会超时→reload→换码)
- 课表:
/xszykb/queryxszykbzong(学期总课表,含 32 位周次位图)、/component/queryKbjg(节次时间) - 成绩:
/cjgl/grcjcx/grcjcx(成绩+排名/考核方式,JSON POST)、/cjgl/grcjcx/getgpa(GPA 概览) - 学业进度:
/cjgl/cjzhtjcx/cjcx/queryXflbyq(学分类别要求)、queryBxkqk(毕业总进度) - 考试:
/kscxtj/queryXsksByxhList(pxn/pxq/ppylx 参数带 p 前缀) - 校历:
/Xiaoli/queryMonthList(全量教学周↔日期映射,需RoleCode头),失败时逐周/component/queryRlZcSj兜底 - 无课教室(贝壳教学平台,与教务系统相互独立):
/general/api/open/building/listBuildings(楼栋)、/general/api/open/teachingCycle/listNodeTypes(节次类型)、/general/api/classroom/freeClassRooms(POST,nodeId传空串=全部时段)。每个请求都要带csrkToken签名:密钥在/config.json的加密domainConfig里(AES-256-CBC),时间戳必须用服务器时间——直接取/config.json的 HTTPDate响应头,详见docs/JWXT_API.md附录 - 登录页是 PC 布局且其 meta viewport 会覆盖 WebView 宽视口设置,注入脚本改写
width=1440并修补100vh高度坍缩(详见JwWebView.kt注释) - 站内跳转按域名白名单收口:
ustb.edu.cn子域 http 与 https 同等放行(统一认证授权成功后的回调 302 会指向http://sso.ustb.edu.cn/idp/thirdAuth/...,拒绝非 https 会把这次跳转静默丢弃,界面永久卡在"授权成功"),站外一律交系统浏览器,无 host/非 http(s) 一律不加载(详见JwWebView.kt的jwNavPolicy) - 全量接口参考:docs/JWXT_API.md(后续开发勿再扒站)
./gradlew assembleRelease # 需要 keystore.properties(签名配置,未入库)
./gradlew testDebugUnitTest # 解析器/周次逻辑单测(fixture 为真实接口样本,见 docs/samples/)更多技术细节见 docs/TECH_DESIGN.md 与 docs/JWXT_API.md。
学号密码只在系统 WebView 中由本人输入给学校统一认证页面,App 不读取、不存储密码;成绩默认隐藏展示需点小眼睛查看。
本机数据:课表/成绩/日程默认仅保存在本机,不上传云端。登录会话由系统 WebView 保存,学号等学籍信息会随成绩抓取缓存在本机用于离线展示;如需终止会话,可在「我的」→「退出教务登录」清除。
云同步(opt-in,默认关闭):在「我的」→「云同步」用教务统一认证(扫码或账密)登录后才会开启。账号即学号、无密码——统一认证成功本身即是身份验证。开启后课表、日程、成绩、考试与相关设置会整包备份到开发者自有服务器(仅用于备份恢复,不做任何分析或共享),全程 HTTPS:https://api.caeamer.com 由 Cloudflare 承接,源站不开放任何公网入站端口(cloudflared 从服务器主动向 Cloudflare 出连,请求经隧道回到本机 127.0.0.1:3000)。关闭开关或退出云账号即停止上传;云端备份在退出云账号后保留,重新登录即可找回。
云账号的身份校验:登录时 App 会把本次教务会话(SESSION cookie)用服务端 RSA 公钥密封(RSA-OAEP-SHA256,密文上传,链路上拿不到明文,也不会被记录),服务端解开后立即向教务系统 /user/me 核实"请求方确实持有该学号的教务会话",核实完即丢弃;只凭"学号+姓名"无法再换到 token(这两项在班级名单/群文件里总是成对出现)。云 token 本身用 AndroidKeyStore 的 AES-256-GCM 加密后落盘,换设备无法解密复用。
云端数据完整性:GET /backup 返回的快照带服务端 Ed25519 签名,恢复前强制验签(无签名/验签失败一律拒绝恢复),避免明文 HTTP 上被整包替换成投毒快照。
查看「无课教室」时 App 会直接访问贝壳教学平台(ustb.smartclass.cn)的公开接口,该请求只携带一个由平台公开配置算出的签名参数与设备网络出口 IP,不包含学号、姓名或任何本机数据。
问题反馈:caeamer@163.com | GitHub Issues
© 2026 caeamer. All rights reserved.