长文档建议按当前分组逐页阅读;右侧辅助栏会保留当前位置和快速操作。
技术原理
深入了解 Mini-HBUT 的架构设计、数据流、跨平台实现机制和核心技术方案。
整体架构
技术栈总览
前端
- • Vue 3 + TypeScript + Composition API
- • Vite 构建工具(端口 1420)
- • Vant 4 UI 组件库
- • Pinia 状态管理
- • 自定义 CSS 变量主题系统
桌面后端(Tauri 2.x)
- • Rust + Tokio 异步运行时
- • reqwest HTTP 客户端
- • scraper HTML 解析器
- • SQLite 本地数据库
- • 本地 HTTP Bridge(127.0.0.1:4399)
移动端(Capacitor 6.x)
- • Android: Gradle + WebView
- • iOS: Xcode + WKWebView
- • Background Fetch 后台任务
- • Local Notifications 本地通知
- • Preferences 持久化存储
云服务
- • Cloudflare Workers 云同步
- • Cloudflare KV 数据存储
- • GitHub Actions CI/CD
- • jsDelivr / unpkg CDN
- • GitCode 远程配置托管
架构图
┌─────────────────────────────────────────────────────────────┐
│ 用户界面 (Vue 3) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 成绩查询 │ │ 课表管理 │ │ 考试安排 │ │ ...更多 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └────────────┼────────────┼────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 平台桥接层 │ │
│ │ (src/platform/) │ │
│ └───┬──────┬───┬───┘ │
└──────────────┼──────┼───┼───────────────────────────────────┘
│ │ │
┌──────────▼──┐ ┌─▼───▼──────┐ ┌────────────┐
│ Tauri 2.x │ │ Capacitor │ │ Web 模式 │
│ (Rust) │ │ (Native) │ │ (Browser) │
│ │ │ │ │ │
│ • HTTP客户端 │ │ • Native │ │ • Fetch │
│ • SQLite │ │ Plugins │ │ • IndexedDB│
│ • Bridge │ │ • BGFetch │ │ │
└──────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ 教务系统 / 电费系统 / ... │
│ (学校内网服务) │
└──────────────────────────────────────────┘
│
┌──────▼──────────────────────────────────┐
│ 云服务层 │
│ • Cloudflare Workers (云同步) │
│ • OCR 服务 (验证码识别) │
│ • 远程配置 (GitCode/GitHub) │
│ • CDN (jsDelivr/unpkg) │
└─────────────────────────────────────────┘跨平台桥接机制
Mini-HBUT 的核心设计原则是「一套前端代码,多平台运行」。通过平台桥接层(src/platform/), 前端组件无需关心底层运行环境,所有平台差异在桥接层统一抽象。
运行时检测
src/platform/runtime.ts 负责在启动时自动检测当前运行平台:
// 检测优先级
1. window.__TAURI__ → 'tauri' (桌面端)
2. window.Capacitor → 'capacitor' (移动端)
3. 其他 → 'web' (浏览器)能力抽象
src/platform/types.ts 定义了统一的能力接口,每个平台有对应的适配器实现:
| 能力 | Tauri 实现 | Capacitor 实现 | Web 实现 |
|---|---|---|---|
| 打开链接 | Tauri Shell Plugin | Capacitor Browser | window.open |
| 发送通知 | Tauri Notification | LocalNotifications | Notification API |
| HTTP 请求 | Rust reqwest | Capacitor HTTP | Fetch API |
| 屏幕常亮 | 不支持 | KeepAwake Plugin | 不支持 |
| 文件操作 | Tauri FS Plugin | Capacitor Filesystem | IndexedDB |
| 分享 | 不支持 | Capacitor Share | 不支持 |
@tauri-apps/api 或 @capacitor/*, 而是统一通过 src/platform/ 暴露的接口调用。新增原生能力必须先在 types.ts 中定义接口, 再在各适配器中实现。登录与会话管理
CAS 登录流程
新融合门户使用 CAS 单点登录协议,完整流程如下:
客户端 教务系统 OCR 服务
│ │ │
│ 1. GET 登录页面 │ │
│ ─────────────────────▶ │ │
│ 2. 返回HTML+验证码图片 │ │
│ ◀───────────────────── │ │
│ │ │
│ 3. 发送验证码图片(base64)│ │
│ ──────────────────────────────────────────────▶ │
│ 4. 返回识别结果 │ │
│ ◀────────────────────────────────────────────── │
│ │ │
│ 5. POST 登录请求 │ │
│ (账号+密码+验证码) │ │
│ ─────────────────────▶ │ │
│ 6. 返回Cookie/重定向 │ │
│ ◀───────────────────── │ │
│ │ │
│ 7. 保存Cookie到本地 │ │
│ 8. 获取最新成绩/课表 │ │
│ ─────────────────────▶ │ │会话恢复策略
应用启动时会按以下优先级恢复登录状态:
- 本地 Cookie 恢复:尝试使用本地存储的 Cookie 直接访问教务系统,如果 Cookie 仍有效则直接进入。
- 最新会话恢复:如果 Cookie 失效,尝试读取最近一次会话的凭据重新建立连接。
- 自动重登:如果保存了密码(非加密临时登录),使用保存的密码自动完成登录流程。
- 引导登录:如果以上都失败,展示登录页面让用户手动登录。
学习通登录
学习通登录使用超星教育平台 API:
- 密码使用 AES-128-CBC 加密后传输。
- 登录成功后通过 SSO 关联到学校教务系统。
- 学号解析策略:payload 解析 → 缓存读取 → API 查询 → 降级处理。
扫码登录
扫码登录流程(门户/学习通通用):
1. qr_init_login() → 获取二维码 → 显示在页面上
2. qr_check_status() → 轮询状态 → 等待用户扫码确认
3. qr_confirm_login() → 确认登录 → 获取 Cookie → 进入首页数据流与缓存
请求-缓存-降级 三层策略
Mini-HBUT 所有数据请求遵循统一的三层策略:
用户请求
│
▼
┌──────────────┐ 命中 ┌────────────┐
│ 检查前端缓存 │────────▶│ 返回缓存数据 │
│ (localStorage)│ │ (标注来源) │
└──────┬───────┘ 未命中 └────────────┘
│
▼
┌──────────────┐ 成功 ┌─────────────┐
│ 网络请求 │────────▶│ 更新缓存 │
│ (API/Bridge) │ │ 返回新数据 │
└──────┬───────┘ 失败 └─────────────┘
│
▼
┌──────────────┐ 有数据 ┌─────────────┐
│ 检查后端缓存 │────────▶│ 返回离线数据 │
│ (SQLite) │ │ 标注"离线" │
└──────┬───────┘ 无数据 └─────────────┘
│
▼
┌──────────────┐
│ 显示错误提示 │
└──────────────┘缓存 TTL 策略
| 缓存级别 | TTL | 适用场景 | 缓存键前缀 |
|---|---|---|---|
SHORT_TTL | 30 秒 | 排名、实时余额 | ranking: |
DEFAULT_TTL | 5 分钟 | 常规查询 | 默认 |
LONG_TTL | 3 天 | 成绩、课表、考试 | grades: / schedule: / exams: |
EXTRA_LONG_TTL | 7 天 | 校历、培养方案 | calendar: / academic: |
缓存容量限制
- 前端 localStorage 缓存:最多 220 个条目。
- 单个缓存值大小限制:180KB(超过则不缓存)。
- 超出容量时自动淘汰最早过期的缓存条目。
- 后端 SQLite 缓存无条目限制,但会定期清理过期数据。
JWXT 维护模式检测
当教务系统(JWXT)返回维护页面或特定状态码时,前端自动进入维护模式:
- 所有查询自动回退到离线缓存。
- 页面顶部显示维护提示横幅。
- 维护结束后首次请求成功会自动退出维护模式。
HTTP Bridge 原理
Tauri 桌面端在启动时会在本地开启一个 HTTP 服务器(127.0.0.1:4399), 称为 HTTP Bridge。前端通过 Vite 反向代理将 /bridge 路径转发到此服务。
工作原理
┌──────────┐ /api/v2/* ┌──────────┐ /bridge/* ┌──────────────┐
│ Vue 前端 │ ──────────────▶ │ Vite Dev │ ────────────▶ │ Rust HTTP │
│ (组件) │ │ Server │ │ Bridge │
│ │ ◀────────────── │ :1420 │ ◀──────────── │ :4399 │
└──────────┘ JSON 响应 └──────────┘ JSON 响应 └──────┬───────┘
│
┌───────────────────┘
│
┌──────────────▼──────────────┐
│ Rust HTTP Client │
│ • 教务系统请求 │
│ • 电费系统 SSO │
│ • 一卡通/图书馆 API │
│ • SQLite 读写 │
└──────────────────────────────┘统一响应格式
interface ApiResponse<T> {
success: boolean; // 请求是否成功
data: T | null; // 返回数据(成功时)
error: { // 错误信息(失败时)
kind: string; // 错误类型
message: string; // 错误描述
} | null;
time: string; // 服务端时间戳
}安全边界
- 仅监听
127.0.0.1,不接受外部连接。 - 不开放
0.0.0.0绑定。 - Cookie 和凭据仅保存在本地,不通过 Bridge 暴露。
- 外部程序(如 NoneBot)通过 Bridge 调用时,应自行添加鉴权层。
Tauri Commands(Rust 指令)
所有 Tauri Commands 定义在 src-tauri/src/lib.rs 中, 前端通过 @tauri-apps/api 的 invoke() 方法直接调用。
指令分组
- • get_login_page - 获取登录页面HTML
- • portal_qr_init_login - 门户扫码初始化
- • portal_qr_check_status - 门户扫码状态检查
- • portal_qr_confirm_login - 门户扫码确认
- • chaoxing_qr_init_login - 学习通扫码初始化
- • chaoxing_qr_check_status - 学习通扫码状态
- • chaoxing_qr_confirm_login - 学习通扫码确认
- • chaoxing_password_login - 学习通密码登录
- • sync_grades - 从教务系统同步成绩
- • get_grades_local - 获取本地缓存成绩
- • sync_schedule - 从教务系统同步课表
- • get_schedule_local - 获取本地缓存课表
- • fetch_exams - 查询考试安排
- • fetch_ranking - 查询绩点排名
- • fetch_student_info - 获取学生信息
- • fetch_qxzkb_list - 查询全校课表
- • electricity_query_location - 查询电费房间
- • electricity_query_account - 查询电费余额
- • fetch_transaction_history - 查询交易记录
- • fetch_classrooms - 查询空教室
- • search_library_books - 搜索图书馆藏
- • export_schedule_calendar - 导出课表为ICS
- • cache_remote_image - 缓存远程图片
- • save_export_file - 保存导出文件
- • resource_share_fetch_file_payload_native - 获取共享文件
- • hbut_ai_chat - AI 对话
- • campus_code_qrcode - 生成校园码
- • campus_code_order_status - 查询订单状态
后台任务机制
桌面端(Tauri)
桌面端在应用启动时执行一次全量检查,此后按照用户设置的间隔定期轮询。检查逻辑运行在 Rust 后端的 Tokio 异步任务中。
移动端(Capacitor)
移动端使用 @transistorsoft/capacitor-background-fetch 插件实现后台任务。
| 配置项 | 值 | 说明 |
|---|---|---|
minimumFetchInterval | 15 分钟 | 系统允许的最小检查间隔 |
stopOnTerminate | false | 应用被杀后继续运行后台任务 |
startOnBoot | true | 开机后自动启动后台任务 |
enableHeadless | true | 支持无界面后台执行 |
后台检查项
后台唤醒
│
├─▶ 检查新成绩 → 比较本地缓存 → 有新成绩 → 推送通知
│
├─▶ 检查考试 → 解析日期 → 明天有考试 → 推送提醒
│
├─▶ 检查电费 → 查询余额 → 低于10度 → 推送告警
│
└─▶ 刷新课表 → 同步数据 → 更新缓存通知去重
每条通知根据内容生成签名(hash),已推送的签名记录在本地。同一签名的通知在一个周期内不会重复推送,避免用户被反复打扰。
云同步原理
架构
客户端 中转服务 Cloudflare
(Mini-HBUT) (OCR Service) Workers + KV
│ │ │
│ POST /api/cloud-sync │ │
│ {action, student_id, │ │
│ secret_ref, payload} │ │
│ ────────────────────▶ │ │
│ │ POST /sync/upload │
│ │ Authorization: Bearer │
│ │ ──────────────────────▶ │
│ │ │ KV.put(sync:{id})
│ │ ◀──────────────────── │
│ ◀──────────────────── │ │
│ │ │为什么需要中转?
- 安全:Cloudflare Worker 的 API Token 保存在中转服务的 Secrets 中,不下发到客户端,防止泄露。
- 灵活:中转服务可以做额外的校验(如 challenge 认证)、日志记录和限流。
- 兼容:客户端只需对接中转服务的统一接口,无需关心后端存储的变化。
同步数据结构
{
"student_id": "2023123456",
"updated_at": "2026-03-10T12:00:00.000Z",
"reason": "auto_login", // 同步原因
"device_id": "xxxxx-xxxxx", // 设备标识
"client_time": 1741622400000, // 客户端时间戳
"payload": {
"settings": { ... }, // 应用/UI/字体/登录设置
"custom_courses": { ... }, // 自定义课程(按学期)
"academic": { ... } // 成绩/排名/课表缓存
}
}速率限制
Cloudflare Worker 内置基于 KV 的速率限制,通过 rate:{action}:{student_id}:{ip} 键记录上次请求时间, 未超过冷却时间的请求会返回 429(Too Many Requests)。
CDN 缓存与按需加载
为减小安装包体积,Mini-HBUT 将部分大型库改为首次使用时按需下载并缓存:
| 库 | 用途 | 加载时机 | 缓存位置 |
|---|---|---|---|
| pdf.js | PDF 文件在线预览 | 首次打开 PDF 文件 | IndexedDB |
| xgplayer | 视频播放 | 首次播放视频文件 | IndexedDB |
| KaTeX | 数学公式渲染 | 首次使用 AI 对话 | IndexedDB |
| marked-katex | Markdown + LaTeX | 首次使用 AI 对话 | IndexedDB |
| 得意黑字体 | 中文装饰字体 | 用户选择并下载 | IndexedDB |
加载器(cdn_loader.js)会按照配置的 CDN 线路(jsDelivr → unpkg)依次尝试下载, 成功后写入 IndexedDB 持久化。后续使用直接从本地读取,实现「首次联网,后续离线」的体验。
项目结构
tauri-app/
├── src/ # Vue 前端源码
│ ├── App.vue # 主应用组件(路由 + 导航)
│ ├── main.ts # 应用入口
│ ├── components/ # 30+ 业务组件
│ │ ├── Dashboard.vue # 首页仪表盘
│ │ ├── GradeView.vue # 成绩查询
│ │ ├── ScheduleView.vue # 课表管理
│ │ ├── LoginV3.vue # 登录页面
│ │ ├── SettingsView.vue # 设置中心
│ │ └── ...
│ ├── utils/ # 工具函数库
│ │ ├── api.js # API 封装 + 缓存
│ │ ├── app_settings.ts # 应用设置管理
│ │ ├── remote_config.js # 远程配置
│ │ ├── cloud_sync.js # 云同步
│ │ ├── background_fetch.js # 后台任务
│ │ ├── font_settings.ts # 字体系统
│ │ ├── updater.js # 版本更新
│ │ └── ...
│ ├── platform/ # 平台桥接层
│ │ ├── types.ts # 能力接口定义
│ │ ├── native.ts # 原生能力封装
│ │ ├── runtime.ts # 运行时检测
│ │ └── adapters/ # 平台适配器
│ ├── config/ # UI 配置
│ └── styles/ # 样式文件
│
├── src-tauri/ # Rust 后端
│ ├── src/
│ │ ├── lib.rs # 命令入口 + 启动逻辑
│ │ ├── http_client/ # 网络请求模块
│ │ ├── http_server.rs # HTTP Bridge 服务
│ │ └── db.rs # SQLite 数据库
│ ├── tauri.conf.json # Tauri 配置
│ └── Cargo.toml # Rust 依赖
│
├── android/ # Capacitor Android 工程
├── ios/ # Capacitor iOS 工程
├── cloudflare/worker/ # 云同步 Worker
├── public/ # 静态资源
│ └── dormitory_data.json # 宿舍楼层数据
└── remote_config.json # 远程配置示例相关文档
当前页面读完后,可以按下面的交叉链接继续。用户文档、开发者文档、故障排查、参考资料和历史文档会互相补充。