吐槽express 中间件multer
工作不是那么忙,想学一下Express+multer弄一个最简单的文件上传,然后开始npm install,然后开始对着multer官方文档一顿操作。
前台页面最简单的:
<!DOCTYPE html> <html> <head> <style> </style> </head> <body> <form action="/upload" method="POST" enctype="multipart/form-data"> <input type="text" name="username" /> <input type="file" name="avatar" /> <input type="submit" value="提交"> </form> </body> </html>
后台服务启用static静态文件中间件,直接访问,然后提交到/upload,后台代码如下:
const express = require('express') const multer = require('multer') const uuid = require('uuid/v4') const path = require('path') const port = 3000 const app = express() var storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, path.join(__dirname, '../src/uploads')) }, filename: function (req, file, cb) { console.log(file) let extName = path.extname(file.originalname) cb(null, uuid() + extName) } }) var upload = multer({ storage: storage }) app.use(express.static('../src/test')) app.use('/upload', upload.single('avatar'), (req, res) => { // 处理上传之后的逻辑 }) app.listen(port, () => { console.log('server is listening at ' + port) })
就这么简单,它就好像自动就给传上去了,但是不足的地方也是存在的,比如默认配置下:它不给带文件默认的扩展名,什么类型的也都接收,然后也不限制大小。
第一、首先说一下配置路径和怎么给上传之后的文件带上默认扩展名。
upload = multer({}),multer初始化的时候,如果文件路径dest不指定,那么官方文档说文件路径默认就是系统默认当前路径做为文件存储的临时路径,但是我尝试之后,发现没有报错,但是文件给上传到哪里去了,也不知道。满脸黑线!!!
然后按照官方文档,配置一下路径吧,可以直接multer({dest:'路径'}),也可以这样:
var storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, path.join(__dirname, '../src/uploads')) }, filename: function (req, file, cb) { console.log(file) let extName = path.extname(file.originalname) cb(null, uuid() + extName) } }) var upload = multer({ storage: storage })
destination回调函数指定文件上传路径,也可以根据file的不同类型,区分不同的路径,这逻辑都取决于你自己。filename也是回调函数,上传文件怎么命名也都取决于你,它默认上传后不给带原文件扩展名,所以这部分就需要你自己给处理一下了,我这边就先拿到文件原有的扩展名,然后给上传之后的文件加上。
第二、使用fileFilter来过滤允许上传的文件类型。
multer({})配置项中除了storage之外还给出了fileFIlter属性,用来过滤允许上传的文件,它也是一个函数,根据不同的文件后缀名来选择是否允许通过。
var upload = multer({ storage: storage, fileFilter: function (req, file, cb) { let extName = path.extname(file.originalname) if (extName.includes('.png')) { cb(null, true) } else { // req.fileCheckError = {} // req.fileCheckError['fileFilter'] = '只能上传png格式的文件' cb(null, false) } } })
通过cb(null,true),第二个参数是true和false来确定是否允许通过,然后官方文档,还说第一个参数可以指定new Error来抛出一个错误,但是抛出错误之后,恐怕就需要另外写express的错误事件处理了,否则后台报错,前台是看不到东西的。官方又说话了,说multer自己不处理错误,它把错误委托给express处理了,然后用法就要变一变了,应该这样:
app.use('/upload', upload.single('avatar'), (req, res) => { upload(req, res, function (err) { if (err instanceof multer.MulterError) { console.log(err, 'multer error') //处理multer自身的错误 res.send(err) } else if (err) { console.log('normal error', err) // 处理委托给express和上传时express自身的错误 res.send(err.Error) } else if (Object.keys(req.fileCheckError || {}).length > 0) { console.log('req.fileCheckError', req.fileCheckError) res.send(JSON.stringify(req.fileCheckError)) } else { console.log('req', req.file) let resContent = {} resContent['username'] = req.body.username resContent['filename'] = req.file.filename res.send(resContent) } }) })
这样写原以为cb(new Error("错误信息"),false)的错误信息会交给multer的错误处理呢,谁知道人家给委托给express的错误处理了,即便是这样,它给的是一个错误,直接扔给前端,我想要的是一个友好的提示信息,而不是扔出一大坨错误信息,那就没有办法了吗?
这个时候,有高手给出招了,说你既然中间件可以对req进行操作,为什么不对req中增加一个属性呢?一句点醒梦中人,然后fileFilter中就给req加了fileCheckError,然后再express的错误处理的时候判断一下,如果有,就友好的交给前端。
三、限制大小。
multer({limits:{}}),options里面有一个limits配置项,它同样是一个对象里面有一堆配置的东西:
fieldNameSize |
Max field name size | 100 bytes |
fieldSize |
Max field value size | 1MB |
fields |
Max number of non-file fields | Infinity |
fileSize |
For multipart forms, the max file size (in bytes) | Infinity |
files |
For multipart forms, the max number of file fields | Infinity |
parts |
For multipart forms, the max number of parts (fields + files) | Infinity |
headerPairs |
For multipart forms, the max number of header key=>value pairs to parse | 2000 |
主要用到的也就是fileSize吧,超过设置大小,multer自动抛出错误到上面的multer自身处理错误中去,然后友好的发给前端就好了。
当然,以上说的这些错误,前端就可以判断了,比如类型、大小,但是都说前端的校验容易被篡改被突破,所以后端还是要校验的,毕竟网络安全无小事,万一呢。