基于Multer+Express+Node.js实现前端查看最近上传文件的问题求助
Express+Multer实现文件上传后前端查看已上传文件
问题背景
已通过Multer、Express.js和Node.js完成文件上传功能,想要在前端添加查看按钮以展示最近上传的文件,但尝试以下代码后出现错误:
let fname = multer.diskStorage({ filename: (req, file, cb) => { cb(file.originalname); }, }); app.get("/", (req, res) => { res.render("uploads"); res.render(fname.toString("utf8")); });
报错信息:
Error: Failed to lookup view "[object Object]" in views directory "D:\OODU\uploadImagesNodejs\views"
原代码文件
index.js
const express = require("express"); const app = express(); const path = require("path"); //Importing required libraries const multer = require("multer"); const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, "images"); //creating a storage destination for the uploaded files }, filename: (req, file, cb) => { console.log(file); cb( null, file.originalname.replace(/\.[^/.]/, "") + "_" + Date.now() + path.extname(file.originalname) //Adding timestamp to the file name to make it as an unique ID ); }, }); const maxSize = 2 * 1024 * 1024; //Limiting the file size within 5MB to upload let upload = multer({ storage: storage, //File conditions for uploading (i.e: size, type) limits: { fileSize: maxSize, }, fileFilter: function (req, file, cb) { let filetypes = /jpeg|jpg|png/; let mimetype = filetypes.test(file.mimetype); let extname = filetypes.test(path.extname(file.originalname).toLowerCase()); if (mimetype && extname) { return cb(null, true); } cb( "Error: File upload only supports the following filetypes: " + filetypes ); }, }).single("image"); app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); let fname = multer.diskStorage({ filename: (req, file, cb) => { cb(file.originalname); }, }); app.get("/", (req, res) => { res.render("uploads"); res.render(fname.toString("utf8")); }); app.post("/uploads", (req, res, next) => { upload(req, res, function (err) { if (err) { if (err instanceof multer.MulterError && err.code == "LIMIT_FILE_SIZE") { return res.send("file exceeds the allowed limit"); } res.send(err); } else { res.send("uploaded successfully"); } }); }); app.listen(3001); console.log("3001 is the port");
uploads.ejs
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body style="justify-content: center; text-align: center"> <form action="/uploads" method="post" enctype="multipart/form-data"> <div><b style="color: blue"> File size should be within 2mb</b></div> <input type="file" name="image" /> <input type="submit" value="submit" /> <a href="/images" class="btn"> view uploads </a> </form> </body> </html>
错误分析
fname是Multer的磁盘存储配置对象,调用toString()只会返回[object Object],而res.render需要传入视图文件名,因此触发视图找不到的错误。- 单个HTTP请求只能调用一次
res.render,第一次调用已发送响应,第二次调用无效。 - 未将上传文件目录设为静态资源,浏览器无法直接访问文件;同时
/images路由未做处理,点击查看按钮无法加载内容。
解决方案
1. 配置静态资源托管
将存放上传文件的images目录设置为静态资源,让浏览器可以直接访问其中的文件:
app.use('/images', express.static(path.join(__dirname, 'images')));
2. 修改后端路由,传递文件列表给前端
读取images目录下的文件,按修改时间倒序排序(最近上传的在前),将文件列表传递给EJS视图:
const fs = require('fs'); // 新增fs模块引入 app.get("/", (req, res) => { const imagesDir = path.join(__dirname, 'images'); fs.readdir(imagesDir, (err, files) => { if (err) { return res.send('读取文件列表失败'); } // 过滤图片文件并按修改时间排序 const imageFiles = files.filter(file => /\.(jpeg|jpg|png)$/i.test(file)); const sortedFiles = imageFiles.map(file => ({ name: file, url: `/images/${file}` })).sort((a, b) => { const statA = fs.statSync(path.join(imagesDir, a.name)); const statB = fs.statSync(path.join(imagesDir, b.name)); return statB.mtime.getTime() - statA.mtime.getTime(); }); // 将文件列表传给视图 res.render("uploads", { files: sortedFiles }); }); });
3. 修改前端视图,展示文件列表
更新uploads.ejs,添加文件列表展示区域,点击按钮可切换显示/隐藏:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>文件上传与查看</title> <style> .file-item { margin: 15px 0; } .file-item img { max-width: 200px; max-height: 200px; border-radius: 4px; } .btn { padding: 6px 12px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; } </style> </head> <body style="max-width: 800px; margin: 20px auto; text-align: center"> <form action="/uploads" method="post" enctype="multipart/form-data"> <div><b style="color: blue"> 文件大小不能超过2MB</b></div> <input type="file" name="image" accept="image/jpeg,image/png,image/jpg" style="margin: 10px 0" /> <input type="submit" value="上传" class="btn" /> <button type="button" onclick="toggleFileList()" class="btn" style="margin-left: 10px">查看上传文件</button> </form> <!-- 文件列表区域 --> <div id="fileList" style="display: none; margin-top: 30px;"> <h3>最近上传的文件</h3> <% if (files && files.length > 0) { %> <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 20px;"> <% files.forEach(file => { %> <div class="file-item"> <p><%= file.name %></p> <img src="<%= file.url %>" alt="<%= file.name %>" /> </div> <% }) %> </div> <% } else { %> <p>暂无上传文件</p> <% } %> </div> <script> function toggleFileList() { const list = document.getElementById('fileList'); list.style.display = list.style.display === 'none' ? 'block' : 'none'; } </script> </body> </html>
4. 修正完整后端代码
删除无用的fname配置,优化上传成功后的跳转逻辑:
const express = require("express"); const app = express(); const path = require("path"); const multer = require("multer"); const fs = require('fs'); const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, "images"); }, filename: (req, file, cb) => { // 修正正则,正确去除文件名中的扩展名 cb( null, file.originalname.replace(/\.[^/.]+$/, "") + "_" + Date.now() + path.extname(file.originalname) ); }, }); const maxSize = 2 * 1024 * 1024; let upload = multer({ storage: storage, limits: { fileSize: maxSize, }, fileFilter: function (req, file, cb) { let filetypes = /jpeg|jpg|png/; let mimetype = filetypes.test(file.mimetype); let extname = filetypes.test(path.extname(file.originalname).toLowerCase()); if (mimetype && extname) { return cb(null, true); } cb("错误:仅支持以下格式的文件上传:" + filetypes); }, }).single("image"); app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); // 静态资源托管 app.use('/images', express.static(path.join(__dirname, 'images'))); app.get("/", (req, res) => { const imagesDir = path.join(__dirname, 'images'); fs.readdir(imagesDir, (err, files) => { if (err) { return res.send('读取文件列表失败'); } const imageFiles = files.filter(file => /\.(jpeg|jpg|png)$/i.test(file)); const sortedFiles = imageFiles.map(file => ({ name: file, url: `/images/${file}` })).sort((a, b) => { const statA = fs.statSync(path.join(imagesDir, a.name)); const statB = fs.statSync(path.join(imagesDir, b.name)); return statB.mtime.getTime() - statA.mtime.getTime(); }); res.render("uploads", { files: sortedFiles }); }); }); app.post("/uploads", (req, res, next) => { upload(req, res, function (err) { if (err) { if (err instanceof multer.MulterError && err.code == "LIMIT_FILE_SIZE") { return res.send("文件大小超过限制(最大2MB)"); } res.send(err); } else { // 上传成功后重定向回首页,刷新文件列表 res.redirect('/'); } }); }); app.listen(3001); console.log("服务器运行在端口3001");
内容的提问来源于stack exchange,提问作者Arrow-02
相关产品推荐
相关产品推荐

