[筆記] 使用 Multer 實作大頭貼上傳(Part 2)
專案介紹
上一篇「使用 Multer 實作大頭貼上傳(Part 1)」介紹了 Multer,讓客戶端可以上傳大頭照到伺服器端,經過 Sharp 處理圖片尺寸和規格後,將圖片以 Buffer 存至 MongoDB 資料庫中。一旦使用者上傳大頭貼後,也可透過另外兩條路由,刪除和在瀏覽器中讀取大頭貼。
本篇將延續同樣功能的三條路由,嘗試使用不同的檔案儲存和讀取方式,來處理上傳的大頭照:
- 透過 Multer 的
DiskStorage
將檔案存至指定的資料夾(磁碟中) - 存至 MongoDB 的是相對簡單和較不佔空間的「檔案路徑」字串
- 讀取大頭照時,從資料庫取得圖片路徑,透過
fs.createReadStream()
以串流形式讀取檔案、透過pipe()
方法回傳
初始化專案
本篇是延續上一篇的專案做功能上的變更及優化,因此已經安裝且載入好 Multer 模組,並完成初步的設定:👉詳情請見上一篇
設定 Multer
const upload = multer(opts)
上一篇有提到,能透過 multer(opts)
設定和初始化 Multer ,其將回傳的實例(instance)存在 upload
上,供後續使用— multer()
接受一個 opts
物件,它帶有許多可以用來設定的「選項」 ,上一篇中所使用的兩個設定:
- 限制上傳檔案的大小:
limits
- 限制接受的上傳格式:
fileFilter
指定存取位置:storage
本次實作,欲將上傳的檔案存至指定的目錄,因此將額外使用 storage
屬性,透過它可以設定檔案的存取位置,storage
有分兩種:
DiskStorage
:是一個 storage engine,透過它可以將檔案存至磁碟中。使用時,可以使用destination
和filename
屬性設定存取位置和檔案命名
❗️檔案命名: filename
👉若無設定檔案命名的方式,Multer 將隨機給予檔案一個名稱(不包含檔案格式)
👉命名時須自行加上檔案格式,Multer 將不會自動補上
MemoryStorage
:是一個 storage engine,它將上傳的檔案以Buffer
物件存在記憶體中,它不帶有任何可用的選項option
:
設定 DiskStorage
本次實作將使用 DiskStorage 把上傳的大頭貼,依照特定的檔案命名規則,存取至指定的目錄。首先需要使用multer
上的 diskStorage()
方法開始設定
destination
:指定檔案儲存位置
- 帶有一個接收三個參數的函式:
req
、file
、cb
Callback 函式 cb()
會在設定完成後被執行,並接受兩個參數:錯誤訊息(成功設定時帶入null
)及存取檔案的資料夾(設定存取到images
資料夾)
2. filename
:指定檔案命名方式
- 帶有一個接收三個參數的函式:
req
、file
、cb
Callback 函式 cb()
會在設定完成後被執行,並接受兩個參數:錯誤訊息(成功設定時帶入null
)及檔案名稱(設定為日期+原始檔案名稱)
將檔案路徑存至資料庫
新增欄位
要儲存用戶大頭貼檔案路徑到資料庫以前,需要先修改userSchema
:新增一個用來存放路徑的avatar
欄位,其欄位類型為String
:
儲存資料
在 userSchema
中新增好存放大頭貼連結的欄位後,就能在路由中,撰寫程式碼,將圖檔連結存到資料庫中:
req.user
是在透過auth
Middleware 驗證使用者後,從資料庫獲取的用戶物件:存入大頭貼前,該物件上avatar
屬性是空值req.file.path
:帶有該檔案被儲存的路徑資訊,將其存在該用戶的avatar
欄位req.user.save()
:請 Mongoose 將包含大頭貼的用戶資料存至資料庫中
測試功能
透過 Postman 上傳一張封面照到此路徑測試:在 Robo 3T 當中,可以確認該使用者 Document 的 avatar
欄位已經存有大頭貼存取的路徑資訊:
在 Visual Studio Code 當中也能看到一個 images 資料夾被創立,且資料夾中存有一張依照命名方式設定的大頭貼圖檔:
在瀏覽器中讀取大頭照
在瀏覽器透過 GET
方法向 /users/:id/avatar
路徑發出請求,並帶入使用者 id
,以獲取該名使用者的大頭貼,在瀏覽器中呈現:
- 透過使用者
id
到資料庫尋找該名使用者資料 - 若無找到該名使用者,或該名使用者無大頭貼時,丟出錯誤
- 透過 Node.js 提供的
path
核心模組上join()
方法串接和取得該圖片的絕對位置,存在imagePath
中(__dirname
:取得目前 js 檔案的絕對路徑) - 回傳大頭貼時,需要設定
Header
當中的Content-type
,定義回傳的資料格式為 jpg 的圖片檔 - 若
catch
接收到錯誤,設定 Status Code 為 404 (伺服器找不到該資源)回傳
1. 透過緩衝形式回傳:Buffer
第一種方式是透過緩衝形式回傳大頭貼:使用 Node.js 檔案系統(fs)的 readFile()
方法讀取該檔案 — 檔案將先被讀取、暫存至緩衝區(記憶體中),當檔案讀取完成後,才回傳給使用者。
使用緩衝形式傳送的缺點:
- 等待時間:若傳輸的檔案非常大(例如高畫質影片),在真正回傳給使用者前,就需要先等待很久的讀取時間,用戶體驗就會降低。
- 記憶體容量:若伺服器一次接收多個客戶端請求,針對每個請求都需要將檔案的讀取先暫存在緩衝區中,那伺服器的記憶體容量就會不堪負荷
2. 透過串流形式回傳:Stream
第二種方式是透過串流形式回傳大頭貼:使用 Node.js 檔案系統(fs)的 createReadStream()
方法以串流形式讀取檔案 — 可以想成把檔案切成很多小塊(chunk)讀取,當一小區塊被讀取完成,就透過 pipe()
方法回傳。
使用串流形式傳送的優點:
檔案在還沒有完整被讀取完畢前,就開始輸出,這能解決使用緩衝所面臨的時間等待和記憶體空間限制問題。在生活中常見的一個例子是觀看串流影片:用戶不需要等待影片讀取完才能觀看,而是一邊看,影片會一邊被讀取和傳送,省下了等待時間。
測試功能
在瀏覽器中,透過 GET
方法向 /users/:id/avatar
路徑發出請求,並帶入使用者id
,能正確讀取大頭照:
結語
透過這次實作,嘗試運用 Multer 套件中不同的方法,來存取使用者上傳的檔案、過程中也才學習到運用串流和緩衝的形式傳輸檔案,以及使用上的優缺點。
最近在實作時,漸漸可以理解在開發上,為完成某個需求,常會有不同的方式能達成。其實未必哪一種方式才是最好或最不好的,更重要的是去深入了解需求和情境,及各種方式的優缺點,以選出當下最合適的方法來開發。