React+Node.js文件上传报ENOENT无对应文件或目录错误修复
附件上传ENOENT报错修复方案
问题复现
实现附件上传功能时,预期将客户端上传的附件存储到client端public/images文件夹,前后端初始实现代码如下:
前端代码
function AddAttachment() { const history = useHistory(); const {userId, setUserId} = useContext(UserContext); const link = window.location.pathname; const split = link.split("/"); const projectId = split[4]; const [file, setFile] = useState(); const [fileName, setFileName] = useState(""); const saveFile = (e) => { setFile(e.target.files[0]); setFileName(e.target.files[0].name); }; const uploadFile = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append("file", file); formData.append("fileName", fileName); try { const res = await Axios.post( "http://localhost:3001/uplatt", formData, projectId, ).then((response) => { if(response){ window.alert('You have successfully uploaded an image!'); history.push(`/${userId}/viewproject/${projectId}`); } }); } catch (ex) { console.log(ex); } }; return ( <div className="d-flex justify-content-center"> <Card style={{ width: '70%' }}> <input type="file" name="file" onChange={saveFile} /> <button onClick={uploadFile}>Upload</button> </Card> </div> ) }
后端代码
app.use(express.static("./public")); var storage = multer.diskStorage({ destination: (req, file, callBack) => { callBack(null, './public/images/') }, filename: (req,file,callBack) => { callBack(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname)) } }) var upload = multer({ storage: storage }); app.post("/uplatt", upload.single('file'), (req,res) => { if(!req.file){ console.log("No file upload"); } else{ console.log(req.file.filename) var imgsrc = 'http://127.0.0.1:3000/images/' + req.file.filename; var projectId = req.body.projectId; var insertData = "INSERT INTO project_attachment(project_attachment_url, project_id) VALUES (?, ?)" db.query(insertData, [imgsrc, projectId], (err, result) => { if (err){ throw err } else{ console.log("file uploaded") res.send(result); } }) } })
运行时抛出错误:Error: ENOENT: no such file or directory, open 'C:\Users\User\myapp\server\public\images\file-1656055704764.pdf'
排查发现multer存储路径默认基于服务端server目录解析,将destination配置为./client/public后,路径被解析为C:\Users\User\myapp\server\client\public\images\file-1656055704764.pdf,仍然不符合预期。
核心原因
- 相对路径解析偏差:Node.js中
./开头的相对路径是相对于执行node命令的工作目录,不是相对于当前代码文件所在目录,直接写./public/images或./client/public很容易出现路径拼接错误。 - 传参位置错误:Axios.post的第三个参数是请求配置项,不是请求传参位置,当前代码把projectId放在第三个参数位置,后端无法从
req.body中获取到projectId值。 - 目录不存在:multer不会自动创建目标存储目录,如果
public/images目录不存在,会直接抛出ENOENT错误。
修复步骤
- 使用绝对路径定位存储目录
用Node.js内置path模块拼接绝对路径,避免相对路径解析问题。假设你的项目结构为根目录下分client、server两个文件夹,后端入口文件在server目录下,修改multer存储配置如下:// 顶部引入fs和path模块,已引入可跳过 const path = require('path'); const fs = require('fs'); // 拼接client端public/images的绝对路径:__dirname是当前后端文件所在的server目录,../回到项目根目录 const uploadDir = path.join(__dirname, '../client/public/images'); // 检查目录是否存在,不存在则递归创建 if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } var storage = multer.diskStorage({ destination: (req, file, callBack) => { callBack(null, uploadDir) }, filename: (req,file,callBack) => { callBack(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname)) } }) - 修正前端传参逻辑
把projectId添加到FormData中,删除Axios.post第三个位置多余的projectId参数:const uploadFile = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append("file", file); formData.append("fileName", fileName); formData.append("projectId", projectId); // 把projectId加入FormData try { const res = await Axios.post( "http://localhost:3001/uplatt", formData ).then((response) => { if(response){ window.alert('You have successfully uploaded an image!'); history.push(`/${userId}/viewproject/${projectId}`); } }); } catch (ex) { console.log(ex); } }; - 静态资源访问校验
本地开发时React会自动将public目录下的文件作为静态资源服务,存储到client/public/images下的文件可以直接通过http://localhost:3000/images/文件名访问,原有后端拼接imgsrc的逻辑不需要修改。
注意:本地开发时服务端可以直接访问同机器上的client目录,生产环境下不建议把上传文件存在前端项目目录中,应该存储到服务端可管理的静态资源目录或专门的文件存储服务中。
内容的提问来源于stack exchange,提问作者red17
相关产品推荐
相关产品推荐

