choose回调是唯一能读取文件并计算MD5的时机,因仅此时obj.files[0]为标准File实例且含完整二进制数据;before无files属性,done/error已发请求,此时算MD5无意义。
choose回调是唯一能读取文件并计算MD5的时机
因为只有choose回调里的obj.files[0]是标准file实例,含完整二进制数据;before里obj没有files属性,done和error已发完请求——此时再算md5毫无意义。
常见错误:在before里写obj.files[0].size或试图调用FileReader,结果报Cannot read property 'files' of undefined或undefined is not a function。
- 必须在
choose中拿到file = obj.files[0]后立刻启动MD5计算 - 不能依赖
obj.pushFile()——它返回的是上传队列ID映射,不是File对象 - 多文件场景要遍历
obj.files,逐个处理,别只取[0]
用SparkMD5.ArrayBuffer分片计算,避免卡死
大文件(比如300MB)一次性读进内存会爆内存、浏览器假死甚至崩溃。必须切块读取,每次读完一块就追加到SparkMD5.ArrayBuffer实例里。
典型错误:用readAsText或readAsDataURL读大文件,页面直接无响应;或者漏掉spark.end()调用,导致始终拿不到MD5字符串。
- 推荐分片大小:
chunkSize = 2 * 1024 * 1024(2MB),平衡速度与内存压力 - 每次
fileReader.onload触发后,调用spark.append(e.target.result) -
spark.end()只能调用一次,拿到值后立刻存到obj.md5或localStorage,别留着复用 - 切片用
file.slice(start, end),注意end要Math.min(start + chunkSize, file.size)
MD5值怎么传给后端做秒传判断
秒传的核心逻辑是:前端把MD5先发一次查询请求,后端查库命中就直接返回已有文件URL,不走上传流程。所以MD5不能等到上传时才塞进表单——得在choose完成计算后,立刻发起校验请求。
容易忽略的点:obj.upload()是手动触发上传,但秒传时你根本不想上传,所以不能无条件调用它;更不能把MD5塞进data字段等上传时一起发——那样就失去“预判”意义了。
- 在
spark.end()之后,用$.ajax或fetch向/api/check-md5发POST,body带{ md5: md5 } - 后端返回
{ code: 0, url: '/files/xxx.pdf' }就直接渲染成功;返回{ code: 1 }再调obj.upload() - 如果配置了
auto: false,记得obj.upload()只在秒传失败时才调用 - 不要把MD5当文件名传——后端需按真实文件内容校验,不是靠名字匹配
文件名和type校验不能替代MD5
file.name和file.type极易被伪造或误判,比如用户把hack.exe改成report.pdf,file.type可能还是application/pdf;而MD5基于实际字节,哪怕改一个字节,哈希值就完全不同。
常见坑:只校验后缀.pdf就放行,结果上传了个恶意PDF壳;或者用file.type === 'image/jpeg'过滤图片,但浏览器对某些JPG返回空type,导致漏放行。
- 格式校验必须双重:正则匹配
/.pdf$/i+file.type粗筛(仅作辅助) - 同名不同内容的文件,MD5一定不同;同内容不同名的文件,MD5一定相同——这才是秒传的依据
- MD5不适合安全敏感场景(如密码),但文件去重和完整性校验完全够用
- 若项目要求更高强度,可换
SparkMD5.ArrayBuffer为crypto.subtle.digest('SHA-256', ...),但兼容性略差
真正卡住人的从来不是怎么写spark.append(),而是没想清楚「什么时候算」和「算完干什么」——选错回调时机,后面全白搭;算完不立刻查服务端,秒传就变成普通上传。


















