2024年了,express中文件上传有变化吗?

492 阅读6分钟

2024年了,我还在用express。本文将详细介绍在 Express.js 中处理文件上传的几种方法,包括不使用中间件、使用 express-fileuploadmulter

三个方案均为多文件上传,单文件的话数组中传一个就好了

注意!!!不要全局应用任何一个文件上传中间件

不使用中间件处理文件上传

虽然使用中间件可以简化文件上传的处理,但也可以通过手动解析 multipart/form-data 请求来实现文件上传。以下是一个简单的示例。

首先,安装 expressformidable(用于处理 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的不断推行,后面可能还会有兼容各大云厂商存储的中间件/方案的出现。让我们拭目以待吧~

附件完整代码

image.png

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. 文件类型和大小限制

  • 文件类型:代码限制了允许上传的文件类型(如 pngjpgjpeggifpdf)。
  • 文件大小:代码限制了上传文件的大小(如 1 MB)。

4. 文件重命名

  • 为了避免文件名冲突,代码会对文件名进行处理,添加时间戳等信息。