webkitdirectory仅支持单文件夹选择,需用webkitRelativePath保留路径结构,哈希去重须基于内容(首尾64KB+大小)且前后端策略一致,空文件哈希固定,服务端必须二次校验。

用 webkitdirectory 选文件夹,但只能单个
HTML 原生不支持一次选多个文件夹,<input type="file" webkitdirectory> 是目前唯一可靠方式——但它只允许用户选择**一个**文件夹(含全部子文件)。别指望加 multiple 能让它多选目录,那没用。如果业务真需要多个文件夹,得换拖放 API(dataTransfer.items + webkitGetAsEntry()),但兼容性更差、代码更重,不是“上传文件夹”场景的默认解法。
靠 webkitRelativePath 保留目录结构,后端必须配合重建
选中文件夹后,每个 File 对象会带 webkitRelativePath 属性,比如 "src/index.js" 或 "assets/logo.png"。这是你还原原始目录层级的唯一依据。别试图拼接 input.files[0].name 或靠文件名推断路径——它只返回最末级文件名,丢了所有父级信息。
实操建议:
- 遍历
e.target.files,对每个file取file.webkitRelativePath(注意:Safari 和 Chrome 支持,Firefox 不支持该属性) - 把
webkitRelativePath当作相对路径前缀,和服务端约定好上传时一并传过去(如额外字段relative_path) - 后端收到后,不能直接用
filename覆盖写入,必须按这个路径创建子目录再存文件,否则所有文件都挤在根目录下
防重复上传得比内容哈希,不是比文件名或大小
用户拖同一个文件夹两次,或不同路径下有相同内容的文件,仅靠 file.name + file.size 判重必错。浏览器里没有 inode,也没有真实路径,唯一靠谱的是内容指纹。
立即学习“前端免费学习笔记(深入)”;
实操要点:
- 必须用
file.arrayBuffer()+crypto.subtle.digest('SHA-256', ...)计算哈希,且只在 HTTPS 环境下可用 - 大文件别全量读——取前 64KB + 后 64KB + 总大小拼成字符串再哈希,精度够用,性能可控
- 哈希结果转成十六进制字符串后,存在
Map或Set中,每次新增文件先查重,命中就跳过 -
input.value = ''必须做:否则用户选完再点取消、又选同名文件夹,change事件不触发,逻辑直接漏掉
服务端二次校验不可省,前端哈希只是加速过滤
前端计算的哈希只是防止无效请求打到后端,但网络丢包、中间代理篡改、或用户绕过 JS 直接发请求,都会让前端校验失效。所以后端仍要对每个上传文件的内容做一次哈希比对,或至少查数据库是否存在相同哈希值的记录。
容易被忽略的点:
- 前端哈希用的是
arrayBuffer,后端读文件流时也要用相同分块策略(比如也取首尾各 64KB),否则哈希值对不上 - 如果文件夹里有空文件(size === 0),
arrayBuffer()返回空 ArrayBuffer,SHA-256 结果固定,别误判为“所有空文件都重复” - 同一文件夹多次上传,
webkitRelativePath一样,但内容可能已被修改——哈希能捕获这点,而路径比对完全无感



















