浏览器文件传输最容易制造误解的一句话是“支持断点续传”。有人说的是网络闪断后连接自己回来,有人说的是接收页面刷新后进度还在,还有人期待发送电脑重启后也能在后台继续。这三件事的权限和数据条件并不相同。
可靠的设计不会用一个百分比概括全部状态,而是明确回答:谁还保存着原始文件,谁保存了已接收分块,新的连接如何证明它属于旧任务,用户点过取消后是否还会复活。
先区分四种中断
| 中断 | 通常能否自动恢复 | 需要什么 |
|---|---|---|
| 短暂断网,页面都没关 | 可以 | 重连后交换任务状态和缺失区间 |
| 接收端刷新 | 通常可以 | 已收分块和清单持久化在本地 |
| 发送端刷新 | 需要用户参与 | 重新选择原文件并验证摘要 |
| 双方都清除站点数据 | 不可以 | 本地任务和分块已不存在 |
这里的“自动”也有边界。可信设备可以自动重建连接,但浏览器不能绕过权限重新打开本地文件选择器,也不能无提示恢复屏幕捕获。这些限制是安全保证,不是工程偷懒。
接收端要保存什么
每个任务需要一个不可与其他会话混淆的传输 ID,以及发送设备身份、文件大小、修改时间、预期摘要、分块尺寸、已确认区间和任务状态。正文分块可以存入 IndexedDB 或浏览器提供的文件写入能力,任务清单与分块提交要尽量保持一致。
不能只保存“已到 63%”。如果中间缺了一个块,单一偏移量会误以为之前全部连续。区间集合或位图能准确表达哪些块已经可靠落地。刷新后接收端用这些记录重建界面,再向发送端请求缺口。
新的连接怎样接回旧任务
底层 WebRTC 会话刷新后已经是新连接。双方先验证设备身份,再交换未完成任务摘要:传输 ID、文件摘要、总大小和状态版本。匹配后,接收端发送压缩过的缺失区间;发送端确认仍持有同一个源文件,才进入续传。
所有控制消息都带会话 ID 和任务版本。旧连接迟到的“完成”或“取消”不能覆盖新会话。双方同时上线并发起恢复时,应由确定规则选一个协调者,避免建立两个发送循环重复写入。
发送端刷新为什么要重选文件
网页最初读取本地文件,是因为用户在文件选择器里明确授权。刷新后,这个临时文件句柄通常不会自动回到 JavaScript。应用可以保留任务元数据,却不能假装还拿得到源文件字节。
恢复时提示“重新选择原文件”是诚实做法。选择后先比较总大小和快速元数据,再计算或校验完整 SHA-256。验证通过才允许从缺失区间继续。若用户选错,界面应保留接收端进度并给出重新选择机会,而不是删除已有分块。
取消也必须持久化
一个常见边界 bug 是:A 点击取消,连接恰好断开;B 没收到取消,重连后又把任务唤醒。解决方法是把取消写成带版本的终态记录,也就是一条“墓碑”。下次同步时,较新的终态覆盖旧的进行中状态。
墓碑不必永久保存。可以在双方都确认后清理,或设置合理过期时间。但在确认之前,不能仅因为界面关闭就忘记用户意图。类似地,“停止自动重连”也应跨刷新保存,直到用户主动再次连接。
收到所有块后还没结束
分块齐全后,接收端按顺序组装并计算摘要。只有与发送端声明值一致,才能标记为校验通过并交给用户。摘要不符时,可重新请求可疑区间;若无法定位,明确提示文件不可用,不能让损坏文件以成功状态下载。
超大文件的摘要计算也要可恢复或可重放,最好放在 Worker 中避免卡住页面。进度展示应把“传输完成”和“正在校验”分开,用户才不会在最后一步误关页面。
本地续传数据也要有清理规则
未完成分块可能包含敏感内容。应用应告诉用户它们保存在当前浏览器,提供删除未完成任务的入口,并按完成、取消、过期和空间压力清理。任务列表不应在遥测里上传文件名、完整摘要或本地路径。
共享电脑上尤其要注意:离开前清除未完成任务和信任设备;收到的文件也应由用户主动保存到合适位置,而不是长期藏在站点存储中。
真正值得测的续传闭环
一次成功传完只能证明基本路径。完整测试要在 1%、50% 和校验阶段分别刷新接收端;刷新发送端并选择正确、错误、同名不同内容的文件;双方同时刷新;断网后取消;取消消息丢失后重连;存储空间不足;最后一个块损坏。每个场景都要验证进度、状态、磁盘内容和双方提示。
在 uCopy 中,短暂断线与接收端刷新会尽量保留确认进度;发送端刷新后需重新选择源文件完成校验。这样的边界不如一句“永不丢进度”漂亮,却更符合浏览器权限,也更值得信任。