不做md5校验的增量热更新等于将用户本地缓存交由网络和cdn随机处置,下载中断、cdn污染或磁盘错误会导致wgt包损坏却无法察觉,引发白屏、脚本报错等故障;uni.downloadfile仅校验http状态码与连接完整性,不验证文件内容一致性,必须在success回调中用本地计算md5与服务端提供的md5字段比对,否则校验形同虚设。

不做MD5校验的增量热更新,等于把用户本地缓存交给网络和CDN随机处置——下载途中断网、CDN缓存污染、磁盘静默错误,都会让wgt包损坏却毫无察觉,最终表现为白屏、脚本报错或样式错乱,而日志里只有一句模糊的Failed to load script或Unexpected token。
为什么uni.downloadFile不能直接信任返回的tempFilePath
UniApp 的 uni.downloadFile 只校验 HTTP 响应状态码(如 200)和基础连接完整性,不校验文件内容是否与服务端一致。实测中,以下情况会导致tempFilePath指向一个“看似成功下载但实际损坏”的文件:
- CDN 节点返回了旧版本缓存(ETag 失效或配置错误)
- 用户 WiFi 切换到移动网络时 TCP 连接中断并被代理静默重传部分数据
- Android 某些定制 ROM 对
tempFilePath所在目录有写权限限制,导致写入截断
所以必须在success回调里,用本地计算的 MD5 与服务端下发的 MD5 字段比对,否则校验形同虚设。
plus.io.resolveLocalFileSystemURL读取tempFilePath再算MD5的坑
直接用 plus.io.resolveLocalFileSystemURL(downloadResult.tempFilePath) 获取 FileEntry 后调用 file() 读取 Blob 是常见写法,但容易踩三个坑:
-
tempFilePath在 iOS 上是file://路径,在 Android 上可能是/data/user/.../cache/xxx.wgt,resolveLocalFileSystemURL在部分 Android 12+ 设备上会因沙箱策略失败 - 大文件(>10MB)用 FileReader.readAsArrayBuffer 会触发内存峰值,低端机可能 OOM 或卡死
- 没处理
FileReader.onerror,比如路径不存在时静默失败,后续 MD5 比对永远 false
更稳的做法是:用 plus.io.convertLocalFileSystemURL(downloadResult.tempFilePath) 先转成可读 URI,再用 uni.getFileSystemManager().readFile 分块读取(避免内存压力),配合 uniCrypto 的 digest 方法流式计算 MD5:
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: downloadResult.tempFilePath,
encoding: 'base64',
success: res => {
// 注意:base64 编码后长度约增 1.33 倍,需确认是否超内存限制
const md5 = uniCrypto.digest('md5', res.data, 'base64');
if (md5 !== expectedMd5) {
console.error('MD5 mismatch:', md5, 'expected:', expectedMd5);
return;
}
// 继续 install
}
});
服务端必须返回Content-MD5头或独立md5字段
客户端无法凭空知道正确 MD5,必须由服务端在下发 wgt 包时一并提供。推荐两种方式,按可靠性排序:
- 首选:HTTP 响应头带
Content-MD5: xxx(Base64 编码的 MD5 digest),uni.downloadFile的header回调可直接拿到,无需额外请求 - 次选:接口返回 JSON 中包含
md5字段(如{"wgtUrl": "...", "md5": "a1b2c3..."}),但要注意该字段必须和实际下发的wgt文件严格对应——如果 CDN 缓存了旧包,而接口返回新包的 MD5,校验必然失败
切忌用服务端“计算一次再缓存 MD5”这种逻辑,必须每次响应都动态计算真实文件的 MD5,否则缓存穿透或版本错配时校验失效。
校验失败必须支持降级和可追溯
MD5 不匹配不是崩溃的理由,而是需要明确反馈和兜底路径:
- 记录完整上下文到日志:当前 App 版本、
wgtURL、服务端返回的 MD5、本地计算 MD5、设备平台/型号/OS 版本 - 自动回退到上一版可用资源(如有缓存)或提示“更新失败,请稍后重试”,而不是卡在空白页
- 给用户提供“跳过本次更新”按钮(尤其对非强制更新),避免因单次校验失败阻断整个使用流程
- 不要用
eval或require动态加载未校验的 JS —— 即使你做了 MD5,也要确保plus.runtime.install前已验证完毕,因为安装过程本身不可逆
真正难的不是算 MD5,而是让校验逻辑嵌进热更新生命周期里不打断主线程、不干扰用户操作、失败时有路可退——这些细节没对齐,再多的“零 GC”“异步流式”都是纸上谈兵。











