uni-app H5端必须用File.slice()切片,因uni.uploadFile不暴露XMLHttpRequest无法监听进度;需用原生fetch/XHR分片上传,配合MD5生成fileId存localStorage实现断点续传。

uni-app H5端必须用 File.slice() 切片,别碰 uni.uploadFile
直接结论:uni.uploadFile 在 H5 环境下仍是黑盒,不暴露底层 XMLHttpRequest 实例,无法监听上传进度、暂停或恢复。想做断点续传,必须绕过它,自己用原生 fetch 或 XMLHttpRequest 发起分片请求。
切片唯一可靠方式是调用浏览器原生 File.slice()(H5 下 tempFile 就是 File 对象):
-
File.slice(start, end)返回Blob,可直接塞进FormData或作为fetchbody - 不要用
uni.getFileSystemManager().readFile—— 这是小程序/APP 专用 API,H5 不支持,调用会静默失败 - 切片大小建议设为
2 * 1024 * 1024(2MB),WiFi 环境可上到5 * 1024 * 1024,避免单次请求超时
断点状态必须存在 localStorage + 唯一 key,不能只靠文件名
用户刷新页面、关掉标签页后还能续传,靠的是本地记录“这个文件传到第几片了”。但光存 fileName 不行——同名文件可能来自不同用户、不同设备、不同时间。
正确做法是上传前先算整个文件的 MD5(用 spark-md5 或 crypto-js),生成唯一 fileId,再以它为 key 存状态:
- 存的内容至少包括:
{ fileId: 'xxx', uploadedChunks: [0,2,4], totalChunks: 12, uploadTime: Date.now() } - 每次上传前先查 localStorage,跳过已成功上传的分片索引
- 上传失败后更新
uploadedChunks数组,不是覆盖整条记录(防止并发写入丢失) - 注意:MD5 计算要全量读取文件,大文件会卡 UI,务必用
Web Worker或分块计算(SparkMD5.ArrayBuffer)
服务端必须提供分片校验接口,否则客户端“续”不起来
客户端能“续”,前提是服务端知道“哪几片我收过了”。不能靠文件名或路径判断,得靠上传前协商好的唯一标识。
标准握手流程如下:
- 前端先发
POST /api/upload/init,带{ fileMd5: 'xxx', fileName: 'a.mp4', fileSize: 123456789 } - 服务端查数据库或缓存,返回已上传的分片序号列表,比如
{ uploadedChunks: [0,1,3] } - 前端据此跳过这些序号,只上传剩余分片,并在每个分片请求头里带上
X-Chunk-Index: 2、X-File-Id: xxx - 服务端收到分片后,按
fileId + chunkIndex存临时文件,最后合并前再校验总 MD5
如果服务端没这个 /init 接口,或者只返回 success: true,那客户端所谓“续传”只是假象——重传所有分片,和没断点一样。
H5 分片上传的兼容性坑:Safari 的 File.slice() 参数是 start, length
Chrome/Firefox 的 File.slice(start, end) 是闭区间左开右闭;Safari(尤其旧版)的 slice(start, length) 是起始+长度。直接写 file.slice(i * size, (i+1) * size) 在 Safari 下会传错数据甚至报错。
稳妥写法是统一用三参数形式,并做 UA 判断或 feature detect:
const blob = typeof file.slice === 'function' ? file.slice(start, end, type) // 大多数浏览器 : file.webkitSlice?.(start, end, type) || file.mozSlice?.(start, end, type);
更推荐的做法是封装一层 getChunk(file, index, chunkSize),内部处理兼容逻辑,外部只管传索引。
真正麻烦的不是切片本身,而是把“已传哪些片”“当前网络是否稳定”“服务端是否认得这个 fileId”三者对齐。少一个环节,断点就形同虚设。


















