多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从input file获取文件根目录:webkitdirectory与webkitRelativePath实战

从input file获取文件根目录:webkitdirectory与webkitRelativePath实战 简介这份资源围绕前端获取文件根目录这一常见需求展开面向Web开发初学者及需要处理文件上传的PHP、JSP开发者解决如何通过JavaScript读取用户所选文件路径并传递给后端的问题。包内共1个PDF文件大小约26KB内容以代码示例为主完整呈现了HTML表单、JavaScript函数与PHP接收逻辑的配合方式。示例中通过document.getElementById获取file输入框的值再赋值给hidden隐藏域提交后由服务端读取思路直观、便于快速理解前后端数据传递流程。资源同时提醒了浏览器安全限制导致无法获取完整绝对路径、以及文件上传需注意安全防护等要点并建议采用FormData等更现代的上传方式。目前已有1239人学习适合想快速掌握文件路径传递基础写法的读者参考借鉴。1. 从 input file 拿到文件根目录一个被低估的工程细节上传文件时前端拿到的input file对象里到底有没有真实路径答案是没有浏览器出于安全策略只给一个File对象name是文件名webkitRelativePath在目录选择场景下才有值。很多开发者第一次做「上传后按目录归类」的需求时会下意识去读file.path结果拿到undefined这就是典型的翻车现场。这个标题要解决的核心问题很具体在只有input file的前提下如何稳定地拿到文件所在的根目录信息并把它用于后续的路径拼接、分类存储或展示。适合正在做上传模块、批量导入、目录结构还原的前后端工程师尤其是被「浏览器不给路径」卡过的人。2. 浏览器为什么拿不到绝对路径安全模型与可用字段拆解2.1 File 对象里真正能用的三个字段先明确一件事现代浏览器不会把用户磁盘的绝对路径暴露给 JavaScript。这是同源策略和隐私保护的一部分不是 bug也不是某个浏览器偷懒。你能从input file拿到的信息全部封装在File实例上常用的只有三个字段含义是否有目录信息name文件名含扩展名否webkitRelativePath相对路径仅webkitdirectory模式有值是相对根目录size/type字节数与 MIME否webkitRelativePath是唯一带目录结构的字段格式类似myFolder/sub/a.png。注意它只在input typefile webkitdirectory时才有值普通单文件选择永远是空字符串。很多人误以为file.name里会带路径实际上name只返回最后一段文件名。2.2 根目录的两种定义选择根 vs 相对根工程里说「文件根目录」通常指两个不同的东西。第一种是用户在选择目录时点中的那个顶层文件夹比如用户选了projectA那projectA就是根。第二种是相对于某个基准路径的根比如你希望所有文件都归到/uploads/{batchId}/下。前者只能靠webkitRelativePath的第一段来推断后者完全由后端或业务逻辑决定。// 从 webkitRelativePath 提取用户选择的根目录名 function getRootDirName(file) { const rel file.webkitRelativePath; if (!rel) return null; // 普通文件选择没有目录信息 const parts rel.split(/); // parts[0] 就是用户点选的那个顶层文件夹名 return parts.length 1 ? parts[0] : null; } // 用法遍历 input.files const input document.querySelector(#dirInput); input.addEventListener(change, () { const files Array.from(input.files); const root getRootDirName(files[0]); console.log(用户选择的根目录名:, root); });这段代码的逻辑很直白webkitRelativePath用/分隔第一段就是根目录名。参数上唯一需要注意的是parts.length 1的判断如果用户只选了一个空目录或者浏览器行为差异导致路径只有一段返回null比返回错误值更安全。input.files是类数组必须用Array.from或展开运算符转成真数组才能用map、filter。2.3 为什么不能靠 value 属性反推路径input.value在部分旧浏览器里会显示C:\fakepath\xxx.png这个fakepath是刻意伪造的不是真实路径。有些老代码会去split(\\)取目录这在现代浏览器里完全失效而且不同系统分隔符还不一样。血泪经验是任何依赖input.value解析路径的写法都会在某个浏览器版本更新后集体翻车。正确做法是彻底放弃绝对路径的幻想只使用File对象上的标准字段。3. 用 webkitdirectory 还原目录结构最小可跑通实现3.1 前端选择目录并提取根目录要让webkitRelativePath有值input 必须加上webkitdirectory属性。这个属性虽然带前缀但主流浏览器都支持。下面是一个完整的最小实现包含目录选择和根目录提取。!-- 目录选择必须加 webkitdirectory -- input typefile iddirInput webkitdirectory multiple ul idtree/ulconst input document.getElementById(dirInput); const tree document.getElementById(tree); input.addEventListener(change, () { const files Array.from(input.files); if (!files.length) return; // 根目录名取自第一个文件的相对路径首段 const rootName files[0].webkitRelativePath.split(/)[0]; const rootItem document.createElement(li); rootItem.textContent 根目录: ${rootName}; tree.appendChild(rootItem); // 按相对路径还原层级 files.forEach(file { const rel file.webkitRelativePath; // 如 myFolder/sub/a.png const li document.createElement(li); li.textContent ${rel} (${file.size} bytes); tree.appendChild(li); }); });逻辑说明webkitRelativePath天然携带了从根目录开始的完整相对路径所以不需要额外拼接。参数上multiple必须加否则用户只能选一个文件目录选择就失去意义。file.size可以用来做前端预校验比如超过阈值的文件提前拦截避免传到后端才报错。3.2 把根目录信息传给后端FormData 的正确拼法前端拿到根目录名后通常要连同文件一起发给后端。直接用FormData追加文件时文件名默认是file.name会丢掉目录结构。正确做法是手动设置第三个参数为相对路径。async function uploadWithRoot(files, rootName) { const fd new FormData(); fd.append(rootName, rootName); // 单独传根目录名 files.forEach(file { // 第三个参数覆盖默认文件名保留相对路径 fd.append(files, file, file.webkitRelativePath); }); const res await fetch(/api/upload, { method: POST, body: fd }); return res.json(); }这里的关键参数是fd.append(files, file, file.webkitRelativePath)的第三个参数。如果不传后端收到的文件名只有a.png目录信息全丢。传了之后后端在 multipart 解析时能拿到myFolder/sub/a.png这样的路径再配合rootName就能完整还原。注意rootName单独传是因为有些后端框架会把文件名做安全过滤路径里的斜杠可能被转义单独传一份更稳妥。3.3 后端接收并重建根目录以常见的 Node.js 服务端为例用multer接收时file.originalname会保留前端设置的相对路径。下面演示如何把文件落到以根目录名为前缀的存储路径下。const multer require(multer); const path require(path); const fs require(fs); const storage multer.diskStorage({ destination(req, file, cb) { // rootName 从 body 取需保证 multer 先解析字段 const root req.body.rootName || default; const dir path.join(__dirname, uploads, root); fs.mkdirSync(dir, { recursive: true }); cb(null, dir); }, filename(req, file, cb) { // originalname 含相对路径取 basename 避免二次嵌套 const base path.basename(file.originalname); cb(null, base); } }); const upload multer({ storage });逻辑说明destination里用rootName建目录recursive: true保证多级目录自动创建。filename里用path.basename只取文件名因为目录已经由destination决定了。如果直接把originalname当文件名会出现uploads/root/myFolder/sub/a.png这种多层嵌套和预期不符。参数上要注意req.body.rootName的可用性依赖 multer 的字段解析顺序如果拿不到可以把 rootName 放到 URL query 里更保险。4. 避坑与排查根目录提取失败的五个真实原因4.1 现象webkitRelativePath 始终为空字符串原因input 没有加webkitdirectory属性或者用户是通过拖拽方式放入文件。拖拽场景下DataTransferItem拿到的File对象不带相对路径。解决目录选择必须用带webkitdirectory的 input拖拽场景改用webkitGetAsEntry()递归读取目录条目但那套 API 更复杂建议优先引导用户点选。4.2 现象根目录名取到了但文件层级全平铺原因上传时FormData.append没传第三个参数后端只收到文件名。解决确认fd.append(files, file, file.webkitRelativePath)三个参数齐全。排查时可以在浏览器 Network 面板看请求体的filename字段如果只有a.png就是漏了第三个参数。4.3 现象后端建目录报 ENOENT 或权限错误原因fs.mkdirSync没有加recursive: true父目录不存在时直接抛错或者目标路径在只读文件系统上。解决统一加recursive: true并在启动时检查上传根目录的写权限。容器化部署时尤其注意挂载卷的权限别等上线才发现写不进去。4.4 现象中文目录名变成乱码原因multipart 传输时文件名编码不一致前端 UTF-8后端按 latin1 解析。解决后端解析originalname时做一次Buffer.from(name, latin1).toString(utf8)转换或者统一在前后端约定用 encodeURIComponent 编码路径。这个坑在跨平台系统里特别常见属于典型的玄学问题排查半天其实是编码。4.5 现象Safari 下 webkitRelativePath 行为不一致原因不同浏览器对webkitdirectory的实现细节有差异Safari 在某些版本下对空目录和隐藏文件的处理不同。解决不要假设所有浏览器行为一致前端做一次兜底——如果webkitRelativePath为空退化为只用name并在 UI 上提示用户当前浏览器不支持目录结构还原。后悔药就是提前做特性检测别等用户反馈才发现。5. 进阶不依赖 webkitdirectory 的根目录推断与校验技巧有时候你没法控制 input 的属性比如文件来自拖拽、粘贴或者第三方组件。这种情况下还能不能拿到根目录可以退一步用「共同前缀」来推断。把所有文件的webkitRelativePath或手动维护的相对路径收集起来求最长公共目录前缀那个前缀就是事实上的根。// 从一组相对路径中求最长公共目录前缀 function commonRoot(paths) { if (!paths.length) return ; const split paths.map(p p.split(/)); const first split[0]; let i 0; // 逐段比较直到出现不一致 while (i first.length - 1) { const seg first[i]; if (split.every(parts parts[i] seg)) { i; } else { break; } } return first.slice(0, i).join(/); } // 示例 const paths [proj/a/x.png, proj/a/y.png, proj/b/z.png]; console.log(commonRoot(paths)); // 输出 proj这段代码的价值在于即使没有webkitdirectory只要你能拿到任意形式的相对路径就能反推根。参数上注意first.length - 1因为最后一段是文件名不参与目录比较。如果所有文件都在同一层返回空字符串表示没有公共目录。另一个实用技巧是校验根目录名是否合法。用户选择的目录名可能包含特殊字符直接拼到后端路径里会有注入风险。建议做一次白名单过滤function safeRootName(name) { // 只允许字母、数字、中文、下划线、短横线 return /^[\w\u4e00-\u9fa5-]$/.test(name) ? name : unnamed; }这个正则覆盖了常见的中英文目录名把../这类危险字符直接挡掉。别小看这一步路径穿越漏洞很多时候就是从文件名拼接进来的。我自己的习惯是任何涉及用户输入拼路径的地方先过一遍safeRootName再进path.join。这个习惯帮我省过至少两次安全审计的麻烦。根目录提取本身不复杂复杂的是边界情况和跨浏览器差异把兜底逻辑写在前头后面就少熬夜。希望帮到你。本文还有配套的精品资源点击获取
返回列表