都2024年了,我还在用express。本文将详细介绍在 Express.js 中处理文件上传的几种方法,包括不使用中间件、使用 express-fileupload 和 multer。
三个方案均为多文件上传,单文件的话数组中传一个就好了
注意!!!
不要全局应用任何一个文件上传中间件
不使用中间件处理文件上传
虽然使用中间件可以简化文件上传的处理,但也可以通过手动解析 multipart/form-data 请求来实现文件上传。以下是一个简单的示例。
首先,安装 express 和 formidable(用于处理 multipart/form-data):
npm install express formidable
然后,创建一个简单的 Express 应用,并配置 formidable 处理文件上传:
const express = require('express');
const formidable = require('formidable');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
// 确保 uploads 目录存在
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads');
}
// 使用 formidable 处理文件上传的路由
app.post('/upload/formidable', (req, res) => {
const form = new formidable.IncomingForm();
form.uploadDir = 'uploads'; // 文件上传目录
form.keepExtensions = true; // 保留文件扩展名
form.parse(req, (err, fields, files) => {
if (err) {
return res.status(500).send(err.message);
}
// 遍历所有上传的文件
const fileArray = Array.isArray(files.file) ? files.file : [files.file];
const promises = fileArray.map((file) => {
const oldPath = file.filepath;
const extension = path.extname(file.originalFilename); // 获取扩展名
const newFilename = file.newFilename + extension; // 添加扩展名
const newPath = path.join(form.uploadDir, newFilename);
return new Promise((resolve, reject) => {
fs.rename(oldPath, newPath, (err) => {
if (err) {
reject(err);
} else {
console.log(`文件上传到 ${newPath}`); // 打印文件路径
resolve();
}
});
});
});
Promise.all(promises)
.then(() => {
res.send('文件使用 formidable 成功上传');
})
.catch((err) => {
res.status(500).send(err.message);
});
});
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器启动在 http://localhost:${PORT}`);
});
确保在项目根目录下创建一个名为 uploads 的目录,用于存储上传的文件:
mkdir uploads
使用 express-fileupload 处理文件上传
express-fileupload 是一个简单易用的中间件,用于处理文件上传。以下是详细步骤。
安装依赖
npm install express express-fileupload
设置 Express 应用
const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
// 使用 express-fileupload 中间件
app.use(fileUpload());
// 处理文件上传的路由
app.post('/upload', (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('No files were uploaded.');
}
// 获取上传的文件
let uploadedFile = req.files.file;
// 使用 mv() 方法将文件移动到指定目录
uploadedFile.mv('uploads/' + uploadedFile.name, (err) => {
if (err) {
return res.status(500).send(err);
}
res.send('File uploaded successfully');
});
});
// 启动服务器
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
确保在项目根目录下创建一个名为 uploads 的目录:
mkdir uploads
使用 multer 处理文件上传
multer 是一个功能强大的中间件,用于处理 multipart/form-data,主要用于上传文件。以下是详细步骤。
安装依赖
npm install express multer
设置 Express 应用
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// 设置文件存储配置
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // 文件上传目录
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage });
// 处理文件上传的路由
app.post('/upload', upload.single('file'), (req, res) => {
try {
res.send('File uploaded successfully');
} catch (err) {
res.sendStatus(400).send('Error uploading file');
}
});
// 启动服务器
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
确保在项目根目录下创建一个名为 uploads 的目录:
mkdir uploads
| 特性 | 不使用中间件 | 使用 express-fileupload | 使用 multer |
|---|---|---|---|
| 易用性 | 需要手动解析 multipart/form-data 请求,较复杂 | 简单易用,只需配置并使用中间件 | 功能强大,但需要更多配置 |
| 安装和设置 | 需要安装 formidable | 需要安装 express-fileupload | 需要安装 multer |
| 配置灵活性 | 配置灵活,但需要手动处理 | 配置简单,适合快速实现文件上传 | 配置灵活,适合复杂场景 |
| 文件存储 | 需要手动指定存储路径和文件名 | 自动处理文件存储,支持 mv 方法 | 提供多种存储选项(磁盘、内存等) |
| 文件大小限制 | 需要手动实现大小限制 | 提供 limits 配置项 | 提供 limits 配置项 |
| 错误处理 | 需要手动处理解析和存储过程中的错误 | 自动处理大部分错误,但可自定义 | 自动处理大部分错误,但可自定义 |
| 支持多文件上传 | 需要手动处理 | 支持多文件上传 | 支持多文件上传 |
| 适用场景 | 适合简单场景或需要完全自定义的场景 | 适合快速实现文件上传 | 适合复杂场景和需要更多控制的场景 |
| 社区支持和文档 | 较少 | 较多 | 非常多,文档详细 |
总结
- 不使用中间件:适合需要完全控制文件上传过程的场景,但实现起来相对复杂,需要手动处理
multipart/form-data请求和文件存储。 - 使用
express-fileupload:适合快速实现文件上传的场景,配置简单,易于使用,但在处理复杂需求时可能不够灵活。 - 使用
multer:功能强大,适合处理复杂的文件上传需求,提供了多种存储选项和配置选项,但设置和使用相对复杂。
和几年前相比,主流方案并没有变化依旧是这两个中间件二选一。不过随着serverless的不断推行,后面可能还会有兼容各大云厂商存储的中间件/方案的出现。让我们拭目以待吧~
附件完整代码
const express = require("express");
const formidable = require("formidable");
const fileUpload = require("express-fileupload");
const multer = require("multer");
const fs = require("fs");
const path = require("path");
const app = express();
const PORT = 3000;
// 配置
const config = {
uploadDir: process.cwd() + "/uploads/",
multer: {
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, process.cwd() + "/uploads/");
},
filename: (req, file, cb) => {
const originalName = encodeURIComponent(
path.parse(file.originalname).name
).replace(/[^a-zA-Z0-9]/g, "");
const timestamp = Date.now();
const extension = path.extname(file.originalname).toLowerCase();
cb(null, `${originalName}_${timestamp}${extension}`);
},
}),
limits: { fileSize: 1 * 1024 * 1024 }, // 1 MB
fileFilter: (req, file, cb) => {
const acceptableExtensions = ["png", "jpg", "jpeg", "gif", "pdf"];
const fileExtension = path.extname(file.originalname).toLowerCase();
if (!acceptableExtensions.includes(fileExtension.slice(1))) {
return cb(
new Error(
`文件扩展名不允许,允许的扩展名有:${acceptableExtensions.join(
", "
)}`
)
);
}
cb(null, true);
},
},
};
// 确保 uploads 目录存在
if (!fs.existsSync(config.uploadDir)) {
fs.mkdirSync(config.uploadDir);
}
// 使用 formidable 处理文件上传的路由
app.post("/upload/formidable", (req, res) => {
const form = new formidable.IncomingForm({
uploadDir: config.uploadDir,
keepExtensions: true,
});
form.parse(req, (err, fields, files) => {
if (err) {
return res.status(500).send(err.message);
}
const fileArray = Array.isArray(files.file) ? files.file : [files.file];
const promises = fileArray.map((file) => {
const oldPath = file.filepath;
const extension = path.extname(file.originalFilename);
const newFilename = file.newFilename + extension;
const newPath = path.join(config.uploadDir, newFilename);
return new Promise((resolve, reject) => {
fs.rename(oldPath, newPath, (err) => {
if (err) {
reject(err);
} else {
console.log(`文件上传到 ${newPath}`);
resolve();
}
});
});
});
Promise.all(promises)
.then(() => {
res.send("文件使用 formidable 成功上传");
})
.catch((err) => {
res.status(500).send(err.message);
});
});
});
// 使用 express-fileupload 处理文件上传的路由
app.post("/upload/express-fileupload", fileUpload(), (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send("没有文件被上传");
}
const fileArray = Array.isArray(req.files.file)
? req.files.file
: [req.files.file];
const promises = fileArray.map((uploadedFile) => {
const filePath = path.join(config.uploadDir, uploadedFile.name);
return new Promise((resolve, reject) => {
uploadedFile.mv(filePath, (err) => {
if (err) {
reject(err);
} else {
console.log(`文件上传到 ${filePath}`);
resolve();
}
});
});
});
Promise.all(promises)
.then(() => {
res.send("文件使用 express-fileupload 成功上传");
})
.catch((err) => {
res.status(500).send(err.message);
});
});
// 使用 multer 处理文件上传的路由
const upload = multer(config.multer);
app.post("/upload/multer", upload.array("file"), (req, res) => {
try {
console.log(req.files);
res.send("文件使用 multer 成功上传");
} catch (err) {
res.status(400).send("上传文件时出错");
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器启动在 http://localhost:${PORT}`);
});
1. 基础配置
- 上传目录:所有上传的文件都会存储在服务器的某个目录下(如
uploads/)。 - 目录检查:如果上传目录不存在,代码会自动创建这个目录。
2. 文件上传中间件
- formidable:
- 解析请求中的文件并保存到指定目录。
- 处理文件重命名,确保不会覆盖已有文件。
- 支持多文件上传。
- express-fileupload:
- 简单易用,直接处理和保存上传的文件。
- 也支持多文件上传。
- multer:
- 提供更多配置选项,如文件大小限制、文件类型过滤等。
- 通过自定义存储引擎处理文件的保存和命名。
3. 文件类型和大小限制
- 文件类型:代码限制了允许上传的文件类型(如
png、jpg、jpeg、gif、pdf)。 - 文件大小:代码限制了上传文件的大小(如 1 MB)。
4. 文件重命名
- 为了避免文件名冲突,代码会对文件名进行处理,添加时间戳等信息。