Express の 文件上传

Express の 文件上传

上一篇文章讲了文件下载。其实大家可以自己看 multer 项目文档。我就是照着这个自己写的。
multer处理multipart/form-data类型的POST请求,即处理文件上传。当然没有文件的表单也可以的。

安装 multer

npm install --save multer

单文件上传

multerrequest对象上增加了file或者files属性分别用于接收表单中的单文件或多文件。

  <form action="http://localhost:3000/uploadSingle" method="post" enctype="multipart/form-data">
    <input type="text" name="username" placeholder="请输入姓名"/><br>
    <input type="password" name="password" placeholder="请输入密码"/><br>
    <input type="file" name="resume" id="resume"><br>
    <button type="submit">提交</button>
  </form>
app.post('/uploadSingle', upload.single('resume'), (req, res) => {
  let resume = req.file;
  console.dir(resume);
  let { username, password } = req.body;
  console.log(username);
  console.log(password);
  res.setHeader('Content-Type', 'text/plain'); 
  res.send(JSON.stringify(req.body));
});

首先,express会在当前路径下创建temp文件夹用来存储上传的文件。然后看一下打印的内容

{
  fieldname: 'resume', // html <input type="file"> 标签的 name 属性值
  originalname: 'if.pdf', // 上传的文件名
  encoding: '7bit', 
  mimetype: 'application/pdf', // 文件的媒体类型
  destination: 'temp/', // 文件保存位置
  filename: '2f84eb8d718b7cb27ba9a38bd1d884c1', // 在 destination 中的文件名
  path: 'temp\\2f84eb8d718b7cb27ba9a38bd1d884c1', // 文件全路径
  size: 3020009 // 文件大小,单位:字节
}

很显然的问题是,上传的文件是一个随机名字。为什么?源码中在multer()函数的注释中有这么一段话。大意就是如果调用multer()函数是没有storage参数而只有dest参数,那文件就会以随机名字存储在dest指定的地方。

The StorageEngine specified in storage will be used to store files. If storage is not set and dest is, files will be stored in dest on the local file system with random names. If neither are set, files will be stored in memory.

要解决这个问题,直觉很简单,改文件名不就好了。对✅,改文件名

app.post('/uploadSingle', upload.single('resume'), (req, res) => {
  let resume = req.file;
  console.dir(resume);

  fs.renameSync('temp/' + resume.filename, 'temp/' + resume.originalname);

  res.setHeader('Content-Type', 'text/plain');
  res.send(JSON.stringify(req.body));
});

在这里插入图片描述
当当当当,大功告成。
等等🤔,这样做好像有些无奈,毕竟文件名本身就有,干嘛要多一行代码处理。再者,以后存储位置要改变的话,每个函数里都要改变,也是得不偿失。

磁盘存储

multer对象提供diskStorage方法允许配置在文件系统如何存储上传的文件。这个对象有两个属性

  • destination: 存储上传文件的地方。可以是字符串或函数。如果是字符串类型并且位置不存在,multer就会递归创建。如果既不传字符串也不传函数,就会将文件存储在操作系统的临时目录os.tmpdir()下。windows 的 lenovo 用户临时目录就是👇 在这里插入图片描述
  • filename: 只能是函数。如果不传函数,multer将使用没有扩展名的伪随机的 32 位十六进制字符串命名文件。
  • 上面两个属性,如果传值为函数类型,函数的参数是一样的,分别是
    • requst: 一般的Request对象。
    • file:上传文件相关信息的对象。
    • callback:回调函数。回调函数的第一个参数都是Error,即如果发生异常的异常对象,第二个参数才是保存文件的路径(destination)文件名(filename)。官方文档把这个形参写为cb,搞得我一下子反应不过来啥意思😅

贴一个完整代码

const express = require('express');
const multer = require('multer');
let app = express();

let storage = multer.diskStorage({
  destination: function (req, file, callback) {
  	// 注意这里支持相对路径哦
    callback(null, '../resource');
  },
  filename: function (req, file, callback) {
    callback(null, file.originalname);
  }
});
// 这里使用了属性值的简写形式,不熟悉 js 的同学可能要补补课了。
const upload = multer({ storage });

app.post('/uploadSingle', upload.single('resume'), (req, res) => {
  let resume = req.file;
  console.dir(resume);
  res.setHeader('Content-Type', 'text/plain');
  res.send(JSON.stringify(req.body));
});

app.listen('3000', () => {
  console.log('server started...');
});

再次执行代码,就会在resource目录下出现我们上传的文件。亲测:上传多个同名文件会出现覆盖,所以在定义文件名时可以加个时间戳或者随机数之类的。

多文件上传

🆕新需求:投简历的表单不仅需要上传简历,还需要上传证件照。所以,一个表单应该有两个可以上传文件的地方。

  <form action="http://localhost:3000/uploadMany" method="post" enctype="multipart/form-data">
    <input type="text" name="username" placeholder="请输入姓名"/><br>
    <input type="password" name="password" placeholder="请输入密码"/><br>
    <input type="file" name="resume" id="resume">简历<br>
    <input type="file" name="logo" id="logo2">头像<br>
    <button type="submit">提交</button>
  </form>

在接收文件时,我们需要分别处理不同的文件。
首先,定义要接收哪些文件,每种文件最多几个。使用multer实例的fields方法,该方法接收一个对象数组为参数,每个对象有下面两个属性

  • name:对应表单input type="file"name属性
  • maxCount:最多接收多少个文件。默认是Infinity无限多个😮
const upload = multer({ storage });

let filesToReceive = upload.fields([
  { name: 'resume', maxCount: 1 },
  { name: 'logo', maxCount: 1 },
]);

然后,将filesToReceive传给POST路由处理方法并拿到文件信息。注意拿文件信息时使用的是files属性,files是一个对象,键是字符串,值是数组。

app.post('/uploadMany', filesToReceive, (req, res) => {
  let resume = req.files['resume'][0];
  let logo = req.files['logo'][0];
  console.log(resume);
  console.log(logo);
  res.setHeader('Content-Type', 'text/plain');
  res.send(JSON.stringify(req.body));
});

看一下打印的内容

{
  fieldname: 'logo',
  originalname: 'logo.txt',
  encoding: '7bit',
  mimetype: 'text/plain',
  destination: '../resource',
  filename: 'logo.txt',
  path: '..\\resource\\logo.txt',
  size: 0 // 一个空的txt
}

无文件上传

无文件上传很好理解嘛,只要使用none()方法。下面是一个不上传任何文件的普普通通表单

  <form action="http://localhost:3000/uploadNone" method="post" enctype="multipart/form-data">
    <input type="text" name="username" placeholder="请输入姓名"/><br>
    <input type="password" name="password" placeholder="请输入密码"/><br>
    <button type="submit">提交</button>
  </form>
app.post('/uploadNone', upload.none(), (req, res) => {
  let { username, password } = req.body;
  res.setHeader('Content-Type', 'text/plain');
  res.send(JSON.stringify(req.body));
});

如果你强行上传文件,哎,人家给你报错😕MulterError: Unexpected field

文件限制 limits

看乐上面的内容,你就知道multer很多默认值比较坑,竟然能上传最多Infinity个文件🙄。所以,很有必要对上传的文件进行过滤,避免恶意的表单提交。

我们下面设置上传文件大小最大1024B,即 1KB。在构造multer实例时指定limits对象用来限制文件上传。

  • fileSize:文件最大长度。单位字节。默认无限大
  • files:上传文件最大数量。默认无限大
  • fields:上传对象中非文件字段的最大数量。默认无限大
const upload = multer({ 
  storage,
  limits: {
    // 单位 字节
    fileSize: 1024
  }
});

如果上传超过最大数量,就会报错❌
在这里插入图片描述

文件过滤 FileFilter

那之前的例子好了,如果我们只想让用户上传简历和证件照,就表示我们只接受PDFJPG/JPEG/PNG格式的文件,其他的、可能伤害服务器的文件,比如.sh/.bat/.exe文件,统统拒绝🙅‍或忽略,哪儿来的回哪儿去。
使用fileFilter定制我们的文件过滤。这个方法接收三个参数

  • requst:一般的Request对象。
  • file:包含上传文件相关信息的对象。
  • callback:回调函数,两个参数,第一个参数是Error对象,无异常传null。第二个参数是boolean值,传true表示允许上传,false表示不允许。
const upload = multer({ 
  storage,
  limits: {
    fileSize: 1024
  },
  // 文件过滤器
  fileFilter: function (req, file, callback) {
    if (file.originalname.indexOf('.sh') !== -1 || file.originalname.indexOf('.exe') !== -1) {
      callback(new Error('不允许上传乱七八糟的文件'), false);
    } else if (file.originalname.indexOf('.pdf') !== -1 || file.originalname.indexOf('.jpeg')  !== -1) {
      callback(null, true);
    }
  }
});

我测试上传个exe文件,看看效果哈哈哈
在这里插入图片描述

异常处理

上面对文件做了限制和过滤,其实…不太友好😐毕竟直接把错误异常返回个用户看挺不专业的,所以我们有必要捕捉这样的异常并进行包装。

multer官网中说不要将multer作为全局中间件使用,所以我们上面的例子都没有这么作。现在我们按照官网,将单个文件上传的代码稍微改一改

修改前

const upload = multer({ ... });
app.post('/uploadSingle', upload.single('resume'), (req, res) => {
  let resume = req.file;
  console.dir(resume);
  res.setHeader('Content-Type', 'text/plain');
  res.send(JSON.stringify(req.body));
});

修改后。❗注意这里我修改了上传exe文件时抛出异常的类型

const upload = multer({ 
  storage,
  // 文件限制
  limits: {
    // 最大文件 1024(1KB),单位 字节
    fileSize: 1024
  },
  // 文件过滤器
  fileFilter: function (req, file, callback) {
    if (file.originalname.indexOf('.sh') !== -1 || file.originalname.indexOf('.exe') !== -1) {
      callback(new multer.MulterError('没想到你会上传这个文件', '不允许上传乱七八糟的文件'), false);
    } else if (file.originalname.indexOf('.pdf') !== -1 || file.originalname.indexOf('.jpeg')  !== -1) {
      callback(null, true);
    }
  }
});

let singleUpload = upload.single('resume');

app.post('/uploadSingle', (req, res) => {
  singleUpload(req, res, (err) => {
    // 如果是 Multer 的异常
    if (err instanceof multer.MulterError) {
      res.json(err);    
      return;
    } else if (err) { // 其他异常
      res.json(err);
      return;
    }

    let resume = req.file;
    res.setHeader('Content-Type', 'text/plain');
    res.send(JSON.stringify(req.body));
  });
  
});

再次上传exe文件时,就会报错提示。可以再次封装同全局统一异常返回。
在这里插入图片描述

好了,上传就暂时写到这里

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值