Express文件表單解析中間件 Multer簡介

前言

Express中最常使用的form解析中間件就是body-parser了,但是它明確表示不會支持multipart/form-data類型的表單.

所以在body-parser官方文檔中提供瞭如下的幾個支持multipart/form-data類型的中間件的鏈接,或者只支持multipart/form-data解析的中間件鏈接.

名稱&地址 周下載量 stars
busboy 426,278 1448
multipart 240,921 993
formidable 1,390,361 4735
multer 284,926 5860

統計截止到2018年12月26日

multer依賴busboy所以所以busboy的實際直接下載數量應該要減少28萬😂.

什麼是multipart/form-data類型的表單?

最直觀的解釋就是支持上傳文件的form表單,如果不使用JavaScript中創建的話,顯式的html聲明如下:

<form action="/profile" method="post" enctype="multipart/form-data">
  <input type="file" name="file"/>
  <input type="submit" value="submit">
</form>

上例子中的<input type="file" name="file" >在頁面中的顯示就是爲一個按鈕點擊後可以進行文件選擇.

其次input可以添加multiple="multiple"屬性,這個時候打開的文件選擇框會允許多選文件.

總結一下有如下幾種關係:

  • 一個name對應多個文件
  • 一個name對應一個文件
  • 多個name對應多個單個文件
  • 多個name對應多個文件

順便說一句multer有中文文檔.

正文

特點

  • multer只會解析form設置爲enctype="multipart/form-data"表單.
  • multer可以定製存儲引擎
  • multer會將上傳的信息以及內容掛載到request對象上

    • request.body 保存文本內容
    • request.file 保存單個文件信息以及對應內容(內存存儲模式)
    • request.files 保存多個文件信息以及對應的內容(內存存儲模式)

基本工作流程

  1. 創建一個multer實例
  2. 使用該實例上提供的不同方法獲取不同功能的中間件
  3. 放入到對應的路由中

上傳單個文件實例

引入multer和Express

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

傳入配置參數

const upload = multer({dest:'/uploads'});

注意:dest參數指定了文件輸出的位置,可以詳細指定文件輸出以及儲存後面會將.

使用multer中間件傳遞單個文件

app.get('/',(request,response)=>{

    console.log('get.request.body',request.body);
    console.log('get.request.file',request.file);
    console.log('get.request.files',request.files);

    response.send('<form action="/" enctype="multipart/form-data" method="post">'+
        '<input type="text" name="title"><br>'+
        '<input type="file" name="upload" multiple="multiple"><br>'+
        '<input type="submit" value="Upload">'+
        '</form>')

});

app.post('/',upload.single('upload'),(request,response)=>{

    console.log('post.request.body',request.body);
    console.log('post.request.file',request.file);
    console.log('post.request.files',request.files);

    response.redirect('/');

});

app.listen(8888,()=>{

   console.log('express正在監聽8888端口');

});

這個例子中我們監聽了根路徑,分別處理兩種不同的請求方式,針對get我們響應表單,針對post我們接受上傳的內容.

注意:upload.single('upload')意思是高速multer只接收name是upload的單個文件.

注意:這個例子中input是可以進行多選的,也就是說後端指定了文件數量爲1但是頁面依然上傳了多個,這個時候multer會報錯.

注意:dest指定的路徑爲upload/會將文件保存到根路徑下的upload文件夾中,對於windows系統來說是在運行這個應用對應的盤符下例如F:\uploads\

這個例子中我填寫了一個文本內容,同時上傳了一個文件,輸出結果如下:

post.request.body { title: 'hello world' }
post.request.file { fieldname: 'upload',
  originalname: '硬盤壞道掃描及修復工具Victoria.7z',
  encoding: '7bit',
  mimetype: 'application/octet-stream',
  destination: '/uploads',
  filename: '6bdfc0df998d72e6232d60f790f47ef8',
  path: '\\uploads\\6bdfc0df998d72e6232d60f790f47ef8',
  size: 1033375 }

上傳多個文件實例

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

const upload = multer({dest:'/uploads'});

app.get('/',(request,response)=>{

    console.log('get.request.body',request.body);
    console.log('get.request.file',request.file);
    console.log('get.request.files',request.files);

    response.send('<form action="/" enctype="multipart/form-data" method="post">'+
        '<input type="text" name="title"><br>'+
        '<input type="file" name="upload"><br>'+ // 此處有兩個相同name的input
        '<input type="file" name="upload"><br>'+
        '<input type="submit" value="Upload">'+
        '</form>')

});

app.post('/',upload.array('upload'),(request,response)=>{ // 注意此處使用的中間件和上例中不同

    console.log('post.request.body',request.body);
    console.log('post.request.file',request.file);
    console.log('post.request.files',request.files);

    response.redirect('/');

});

app.listen(8888,()=>{

   console.log('express正在監聽8888端口');

});

在這個例子的表單中有兩個同名的name都是文件類型,這次使用array的方式來進行接受,控制檯輸出內容如下:

post.request.body { title: 'hello world' }
post.request.file undefined
post.request.files [ { fieldname: 'upload',
    originalname: '硬盤壞道掃描及修復工具Victoria.7z',
    encoding: '7bit',
    mimetype: 'application/octet-stream',
    destination: '/uploads',
    filename: '71ed2ac4299d43a30f5c13892f33e51b',
    path: '\\uploads\\71ed2ac4299d43a30f5c13892f33e51b',
    size: 1033375 },
  { fieldname: 'upload',
    originalname: '新建文本文檔.txt',
    encoding: '7bit',
    mimetype: 'text/plain',
    destination: '/uploads',
    filename: '190bde8fcdd08d57648ffb243607ed9d',
    path: '\\uploads\\190bde8fcdd08d57648ffb243607ed9d',
    size: 218 } ]

在上面的例子中刪除掉一個input,將剩餘的input添加multiple屬性用於多選,頁面中在選擇文件框中選擇多個文件也是可以順利通過.

其餘的方法

除了上方提到的multer.single方法外還有其他的幾種方法.

  • array(name:string,maxcount?:number) 根據name限制上傳文件的最大個數
  • fields(fields:object) 自定義限制規則
  • none() 只保留文本信息
  • any() 允許任意類型通過,文件數組將保存在 req.files

實際上它們都可以視爲fields方法的包裝,源碼如下:

Multer.prototype.single = function (name) {
  return this._makeMiddleware([{ name: name, maxCount: 1 }], 'VALUE')
}

Multer.prototype.array = function (name, maxCount) {
  return this._makeMiddleware([{ name: name, maxCount: maxCount }], 'ARRAY')
}

Multer.prototype.fields = function (fields) {
  return this._makeMiddleware(fields, 'OBJECT')
}

Multer.prototype.none = function () {
  return this._makeMiddleware([], 'NONE')
}

Multer.prototype.any = function () {
  function setup () {
    return {
      limits: this.limits,
      preservePath: this.preservePath,
      storage: this.storage,
      fileFilter: this.fileFilter,
      fileStrategy: 'ARRAY'
    }
  }

  return makeMiddleware(setup.bind(this))
}

常用API一覽

來自中文文檔:

multer(opts)

Multer 接受一個 options 對象,其中最基本的是 dest 屬性,這將告訴 Multer 將上傳文件保存在哪。如果你省略 options 對象,這些文件將保存在內存中,永遠不會寫入磁盤。

爲了避免命名衝突,Multer 會修改上傳的文件名。這個重命名功能可以根據您的需要定製。

以下是可以傳遞給 Multer 的選項。

Key Description
dest or storage 在哪裏存儲文件
fileFilter 文件過濾器,控制哪些文件可以被接受
limits 限制上傳的數據
preservePath 保存包含文件名的完整文件路徑

fileFilter

設置一個函數來控制什麼文件可以上傳以及什麼文件應該跳過,這個函數應該看起來像這樣:

function fileFilter (req, file, cb) {

  // 這個函數應該調用 `cb` 用boolean值來
  // 指示是否應接受該文件

  // 拒絕這個文件,使用`false`,像這樣:
  cb(null, false)

  // 接受這個文件,使用`true`,像這樣:
  cb(null, true)

  // 如果有問題,你可以總是這樣發送一個錯誤:
  cb(new Error('I don\'t have a clue!'))

}

錯誤處理機制

當遇到一個錯誤,multer 將會把錯誤發送給 express。你可以使用一個比較好的錯誤展示頁 (express標準方式)。

如果你想捕捉 multer 發出的錯誤,你可以自己調用中間件程序。如果你想捕捉 Multer 錯誤,你可以使用 multer 對象下的 MulterError 類 (即 err instanceof multer.MulterError)。

var multer = require('multer')
var upload = multer().single('avatar')

app.post('/profile', function (req, res) {
  upload(req, res, function (err) {
    if (err instanceof multer.MulterError) {
      // 發生錯誤
    } else if (err) {
      // 發生錯誤
    }

    // 一切都好
  })
})

磁盤存儲引擎 (DiskStorage)

磁盤存儲引擎可以讓你控制文件的存儲。

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/my-uploads')
  },
  filename: function (req, file, cb) {
    cb(null, file.fieldname + '-' + Date.now())
  }
})

var upload = multer({ storage: storage })

有兩個選項可用,destinationfilename。他們都是用來確定文件存儲位置的函數。

destination 是用來確定上傳的文件應該存儲在哪個文件夾中。也可以提供一個 string (例如 '/tmp/uploads')。如果沒有設置 destination,則使用操作系統默認的臨時文件夾。

注意: 如果你提供的 destination 是一個函數,你需要負責創建文件夾。當提供一個字符串,multer 將確保這個文件夾是你創建的。

filename 用於確定文件夾中的文件名的確定。 如果沒有設置 filename,每個文件將設置爲一個隨機文件名,並且是沒有擴展名的。

注意: Multer 不會爲你添加任何擴展名,你的程序應該返回一個完整的文件名。

每個函數都傳遞了請求對象 (req) 和一些關於這個文件的信息 (file),有助於你的決定。

注意 req.body 可能還沒有完全填充,這取決於向客戶端發送字段和文件到服務器的順序。

內存存儲引擎 (MemoryStorage)

內存存儲引擎將文件存儲在內存中的 Buffer 對象,它沒有任何選項。

var storage = multer.memoryStorage()
var upload = multer({ storage: storage })

當使用內存存儲引擎,文件信息將包含一個 buffer 字段,裏面包含了整個文件數據。

警告: 當你使用內存存儲,上傳非常大的文件,或者非常多的小文件,會導致你的應用程序內存溢出。

filefilter和filename還有路由觸發的順序

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

const storage = multer.diskStorage({
    destination:__dirname, // 保存到當前目錄
    filename(request,file,callback){

        console.log('filename:',file);

        callback(null,'newfilename');// 修改上傳的文件名稱
    }
});


const upload = multer({
    dest:'/uploads',
    fileFilter(request,file,cb){

        console.log('fileFilter:',file);

        cb(null,true);

    },
    limits:{
        fileSize:100000 // 限制上傳文件大小爲100000字節
    },
    storage // 使用默認的儲存器
});

最後觸發的順序爲:

  1. filefilter
  2. filename
  3. 我們定義的路由
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章