Express檔案表單解析中介軟體 Multer簡介

ASCll發表於2019-02-16

前言

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. 我們定義的路由

相關文章