跳转至

Android 学习会话、壁纸同步与平板编辑修复

日期:2026-10-22。基线:main / e5b7ac13b649ae33d54c3ea8e044b7e1b675f89b,应用版本 0.11.1。本报告记录初始 0.11.1 修复阶段;这些修复现已随 0.11.2 发布,完整来源、三端包与渠道记录见 0.11.2 发布执行记录。

反馈与证据边界

用户提供的诊断显示 Android 已处于 ready,学习首页取到 125380 字节且找到会话令牌,随后作业双页各返回 2760 字节、没有表单,最终出现会话失效提示。壁纸恢复截图显示 HTTP 403;横屏平板可以直接拖动白板组件。

这组信息能定位失效环节,但不足以把所有现象归于同一个原因:

  • Android 的 Windows Chrome/79 User-Agent 来自现有兼容策略,设备识别需要结合原生平台与触摸能力。
  • 旧诊断只要看到 Wengine 注入变量就标记 webvpn-guide,正常课程页也会命中。125380 字节的页面不能据此判为登录页。
  • 未取得该用户 2760 字节响应正文,本批通过模拟登录壳、网关页和接口异常复现相关错误处理路径。
  • 截图中的 403 正文被截断,无法确认该次错误究竟发生于目录访问还是文件读取。修复保留真实权限检查,并改善不存在备份时的处理。

本次测试全部采用隔离的虚构数据、模拟传输或本机 HTTP 服务,没有访问用户账户,没有写入、删除实际云盘内容。

一、学习会话与网关恢复

已确认的问题

  1. 已经经过 WebVPN 包装的地址,旧 Cookie 引导流程可能把网关地址作为业务域名送入引导接口;并发请求还会共享可变的请求目标。原生 Cookie 写入未等待完成就重放请求,增加短暂失败概率。
  2. JSON 解析失败会产生通用 AuthRequiredError,学习域恢复层只识别其子类 SessionExpiredError。接口返回登录 HTML 时,学习域自己的恢复流程可能被跳过,转而触发全局认证。
  3. 作业查看与提交两个页面的读取曾将失败吞成空字符串;页面没有表单仍能被当作正常详情,用户只看到加载异常或不能提交。
  4. 服务异常、无效 JSON、限流及会话失效缺少明确边界。部分列表读取失败被转换成空列表,容易覆盖上次完整数据。

当前处理

packages/core/src/http.ts 在请求发起时捕获独立的目标地址。引导前还原真实业务域名、协议和路径,并保留路径末尾的斜杠;业务 Cookie 写入实际域名及对应的 WebVPN 应用路径。原生写入完成后再重放请求。无效 Cookie 和网关控制票据不会进入此流程。

学习读取在 LearnClient 内统一检查 HTTP 状态、页面类别和 JSON 结构。真实登录页与 HTTP 401 进入学习域恢复;403、429、5xx、网关占位页和无效响应保留为服务错误。恢复按代次合并并发请求,同一轮共享结果,每个请求最多重试一次。内部的可恢复会话异常暂不广播给全局认证观察器,避免并行启动两套恢复。

作业双页被作为一次读取单元:等待两页完成,验证后解析;会话恢复成功后重新读取两页。通知详情和列表也采用同类检查。有效的空数组仍是合法结果,缺少应有结果容器或传输失败则拒绝本次更新,保留已有完整数据。

学习首页取令牌时同样检查服务错误。首次加载遇到 429、503 或网关页,不会仅因取不到令牌就重新提交账号密码。课程页诊断区分 learn-page-proxied 与真正的登录壳,并增加只含类别与长度的“学习响应”摘要;日志继续省略密码、Cookie、令牌和网页正文。

验证范围

  • tools/http-wengine-bootstrap-test.mjs:10 项,覆盖逻辑地址、已包装地址、应用路径隔离、并发 Referer、原生 Cookie 写入顺序、异常 Cookie、状态检查和认证广播边界。
  • tools/learn-response-recovery-test.mjs:30 项,覆盖页面分类、登录 HTML、并发恢复及失败共享、作业双页恢复、空页面、正文中的登录词语、JSON 结构、有效空列表、首次加载错误、401/403/429/5xx 和隐私边界。发布前补上首次取令牌收到 HTTP 401 的恢复分支;首次 resume 和作业读取均验证只恢复一次,403/429/503 保持服务错误。
  • 学习快照 12 项、学校语言解析 48 项、学校诊断隐私 30 项、网络日志隐私 23 项通过;提交体、附件、拍照上传、URL 与旧会话恢复回归通过。
  • 登录公钥流程 30 项、登录尝试状态 28 项、会话就绪 7 项通过。
  • tools/login-recovery-browser-test.mjs:117 项真实页面操作通过,覆盖手机和桌面的错误展示、双击提交、语言切换、短信/动态口令验证、信任因子限制、成功登录后的延迟服务故障及日志脱敏。运行于本次生产构建,模拟原生 IPC,阻止外部网络。

二、壁纸跨设备备份与恢复

已确认的问题

旧档案直接保存壁纸文档,其中 local:/某台设备/路径 只记录来源设备文件位置,没有上传图片字节。另一台设备即使成功下载 JSON,也无法读取该路径。此外,恢复流程会先确保资料库存在,再直接读固定文件;从未上传过的账户也会进入创建或缺文件读取路径。

当前格式与流程

新增可移植 v3 档案,保存内置壁纸引用、壁纸库元数据及去重后的图片或视频字节。目标为本人 OneTHU/壁纸/onethu-wallpaper-v3.json。独立文件名避免旧客户端读到新引用格式后清空壁纸;原有 onethu-wallpaper.json 保留,恢复优先读取 v3,并兼容旧版内嵌媒体档案。

恢复仅查找本人资料库,依次确认目录和文件存在,不为读取自动建库。没有备份给出明确提示,真实 403 给出权限说明。导入前验证整份档案;媒体保存到当前设备的壁纸目录,全部完成后才替换壁纸文档。期间账户变化或本地壁纸被修改会中止应用;失败时清理本次新建文件。

原生 wallpaper_read 仅能读取应用壁纸目录下的规范化文件,拒绝越界路径及符号链接逃逸。seafile_upload_wallpaper 通过内存上传,固定本人完整 OneTHU 资料库及壁纸路径,不留下上传临时文件。单个媒体上限 12 MiB,整份档案上限 24 MiB;下载按响应头及流式累计大小限额,包含无 Content-Length 的分块响应。超限明确提示,保留现有壁纸。

兼容和操作说明

  • 仅保存来源设备路径的旧备份缺少媒体内容,需要在仍有原图的来源设备更新后重新上传一次,再到另一台设备恢复。
  • 本次加入两个原生命令,前端热重载无法补齐运行中的 Rust 程序。开发环境需要退出并重新启动 pnpm --filter @onethu/desktop tauri:dev;已安装客户端需要包含本批修改的新安装包。
  • 本批保留现有手动上传、恢复入口,没有新增后台自动传输或绕过云盘授权。

验证范围

  • tools/wallpaper-sync-test.mjs:33 项,覆盖跨设备字节往返、媒体去重、内置壁纸、旧档兼容、旧本地路径提示、只读恢复、真实 403、无备份、超限及对应双语提示、非法引用、账户变化、本地编辑冲突、部分写入清理及新旧文件优先级。
  • tools/personal-cloud-target-state-test.mjs:50 项通过,覆盖本人资料库识别与恢复入口。
  • Rust cargo check --lib --offline 通过;原生测试 5 项通过,覆盖正常读取与目录越界、符号链接逃逸、12 MiB 媒体上限、24 MiB 上传上限,以及 Content-Length/分块 HTTP 响应的流式上限。测试使用本次源代码编译的 onethu_lib 测试二进制,全部在本机执行。

三、横屏平板的白板编辑权限

原先以窄屏布局判断是否要求显式编辑,横屏平板超过宽度阈值就继承了桌面直接拖动规则。

现在分别判断布局宽度与触摸交互:Android、iPad、MacIntel 多点触摸及主输入为粗指针的设备均采用显式编辑。宽屏平板保留适合横屏的画布布局,浏览时滚动、点击内容可用,组件的位置与尺寸保持锁定;点击“编辑”后才开放移动和缩放。切换白板、朝向、模式或退出编辑时重置编辑权限。鼠标桌面保留原有操作方式。

实际 React 页面通过浏览器 CDP 触摸事件验收:

  • 新增平板用例 22 项:1280×800、Windows UA 的模拟 Android、浏览滚动、锁定移动、编辑后真实拖动和缩放、组件跳转、横竖屏切换、首页与 OnePage、返回键、模式切换、桌面回归。
  • 既有手机编辑回归 30 项:390px 竖屏与 844px 横屏,含输入法高度变化、编辑入口和英文控件。
  • 平台策略 21 项及相关 CSS、Hook 顺序、白板撤销回归通过。
  • 视觉证据:/tmp/onethu-tablet-edit/tablet-browse.png、tablet-editing.png 和该目录的 report.json;手机回归记录位于 /tmp/onethu-phone-edit-regression/report.json。已人工查看平板浏览、编辑截图。

最终验收与交付边界

前端生产构建通过,存在项目既有的大分块及动态/静态混合导入警告。构建输出位于 /tmp/onethu-android-feedback-build,用于隔离浏览器测试。TypeScript 核心与桌面检查、i18n 中英文各 7582 个键及 62 条 Android 小组件兜底校验通过。

主要无账户复跑命令:

node --import ./tools/ts-resolve-register.mjs tools/http-wengine-bootstrap-test.mjs
node --import ./tools/ts-resolve-register.mjs tools/learn-response-recovery-test.mjs
node tools/wallpaper-sync-test.mjs
node tools/personal-cloud-target-state-test.mjs
pnpm --filter @onethu/core typecheck
pnpm --filter @onethu/desktop typecheck
pnpm i18n:check

旧 tools/board-edit-test.mjs 有 10 条基于源码字符串的过期断言失败,主要仍要求早前已移除的全局预览缓存注入。该项作为既有测试欠账记录;本批没有恢复会污染真实用户数据的预览注入。白板操作以本次真实页面触摸测试和现有手机回归作为验收依据。

尚未在反馈用户的 Android 实机和学校账户上联调;模拟测试验证了修复分支与交互,无法替代实际网关响应和账户权限。后续发布前应在手机与横屏平板分别确认课程、作业双页加载,以及两台已更新设备之间的壁纸上传和恢复。

初始修复阶段保留了原版本和发布渠道;后续按用户要求统一构建并发布 0.11.2。用户原有开发服务保持运行。