在?node?中使用?koa-multer?庫上傳文件的方式詳解
本文主要介紹了上傳單個文件、多個文件,文件數(shù)量大小限制、限制文件上傳類型和對上傳的圖片進行不同大小的裁剪,閱讀本篇文章需要具備一定的 node 和 koa 框架的基礎知識以及 async await 語法,如果遇到不了解的知識點可以自己查閱資料,不了解按照本文的案例也可以實現(xiàn),希望可以通過本篇文章讓你掌握在 node 中實現(xiàn)文件上傳,如果只需要源碼,可在文章最后進行獲取
環(huán)境準備
- 需要本機具備 nodejs 環(huán)境
- postman 接口測試工具,或者其他測試工具都可以
- 需要的依賴如下:
{ "jimp": "^0.22.5", "koa": "^2.14.1", "koa-multer": "^1.0.2", "koa-router": "^12.0.0", "nodemon": "^2.0.20" }
nodemon
:讓項目啟動后,代碼更改自動重新啟動服務,方便調試
當然如果你希望輸入 nodemon 命令就可以啟動項目可以在 package.json 文件中進行如下設置:
{ "name": "node-upload", "version": "1.0.0", "description": "file upload", "main": "./main.js", // 設置為你項目的入口文件路徑 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon" // 設置啟動命令為 nodemon }, "author": "coderjc", "license": "ISC", "dependencies": { "jimp": "^0.22.5", "koa": "^2.14.1", "koa-multer": "^1.0.2", "koa-router": "^12.0.0", "nodemon": "^2.0.20" } }
koa
:是一個新的 web 框架,想在 node 中使用需要這個依賴koa-route
:koa 框架的路由系統(tǒng)采用第三方庫的形式實現(xiàn)koa-multer
:幫助我們在 koa 中進行文件上傳jimp
:將圖片進行不同大小的裁剪
項目初始化
- 本文主為了案例演示,所以不進行對項目進行模塊化的劃分,統(tǒng)一在入口文件中進行演示,初始化入口文件如下:
// 引入內置模塊 const path = require('path') // 引入第三方庫 const Koa = require('koa') const Router = require('koa-router') const multer = require('koa-multer') const Jimp = require('jimp') // 創(chuàng)建app實例 const app = new Koa() // 創(chuàng)建路由實例 // - prefix:配置根路徑 const uploadRouter = new Router({ prefix: '/upload' }) // 注冊接口 app.use(uploadRouter.routes()) // 監(jiān)聽 app.listen(8001, () => { console.log('koa server start success~') })
創(chuàng)建上傳單個文件接口
我們通過創(chuàng)建的路由實例,可以在上面指定 http 請求類型,上傳文件為 post 請求,因為已經(jīng)配置了根路徑,所以上傳單個文件的路徑為 /
,表示為默認路徑,具體路由系統(tǒng)這部分本文不在進行贅述,創(chuàng)建接口如下:
// 單文件上傳接口 uploadRouter.post('/', (ctx, next) => { ctx.body = '文件上傳成功' // ctx.body 方法可以返回指定返回給客戶端的信息 })
上述就是在 koa 中一個接口的創(chuàng)建,我們可以使用 postman 測試一下工具是否搭建成功,postman 的使用方式本文不進行講解,使用很簡單,大家查閱資料即可,結果如下:
返回 文件上傳成功
或自定義的信息,即表示接口搭建成功
使用 koa-multer 實現(xiàn)單文件上傳
- 在實現(xiàn)文件上傳之前,我們需要一個文件夾來存儲我們上傳的文件,在真實的開發(fā)中,一般會有一臺其余的服務器用來存儲文件,本案例就直接用本地代替了,我們在項目的根目錄中創(chuàng)建文件夾
uploads
- 在實現(xiàn)之前,我們先來看一寫參數(shù)解析,摘自官網(wǎng),大家可以放心參考
屬性名 | 描述 |
---|---|
fieldname | 表單中指定的字段名稱 |
originalname | 用戶計算機上文件的名稱 |
encoding | 文件的編碼類型 |
mimetype | 文件的 MIME 類型 |
size | 文件大?。ㄒ?bytes 為單位) |
destination | 保存文件的文件夾 |
filename | 控件中的文件名 |
path | 上傳文件的完整路徑 |
buffer | Buffer 整個文件 |
不帶文件后綴名存儲
在 koa-multer 中實現(xiàn)文件上傳具備兩種寫法:
- 不帶文件后綴名存儲
- 后端代碼:
// 配置 multer const upload = multer({ dest: './uploads' // 文件存儲地址 }) // 使用 single() 方法獲取單張圖片,此方法需要傳遞一個參數(shù),為前端是上傳鍵名一致 // - single 方法會返回一個函數(shù),可以作為一個中間件使用,根據(jù)這個特性,我們可以使用使用一個變量接收返回的函數(shù) const one = upload.single('file') // 將返回的函數(shù) one 作為中間件 uploadRouter.post('/', one, (ctx, next) => { // 如果你需要在后面的中間件獲取文件信息,可以使用 ctx.req.file獲取 ctx.body = { flie: ctx.req.file } })
postman 模擬前端發(fā)送請求
我們來看一下上傳前的 uploads 文件夾,展開沒有任何內容
上傳后 uploads 文件夾內容如下:不過由于沒有添加后綴名,無法直接查看,可以自己添加后綴名進行查看
5. 來看一下返回給客戶端的文件信息具體內容:
文件攜帶后綴名存儲
- 如果需要設置后綴名,我們有兩個選項可用,
destination
和filename
。它們都是確定文件存儲位置的函數(shù) destination
:用于確定存儲文件的地址filename
:用于確定文件夾中的文件命名- 首先我們需要使用配置一下兩個函數(shù),如下:
// 文件配置 // - req 請求信息 // - flie 文件信息 // - cb 回調函數(shù) const storage = multer.diskStorage({ // 存儲路徑 destination: (req, flie, cb) => { // cb: (error: Error | null, destination: string) // - 通過將鼠標放入 cb 上,會出現(xiàn)一個提示,傳入兩個參數(shù) // - 參數(shù)一:傳入一個錯誤 // - 參數(shù)二:傳入存儲路徑 cb(null, './uploads') }, // 設置文件名 filename: (req, flie, cb) => { // path.extname() 方法獲取文件后綴名 // - originalname 用戶計算機上的文件的名稱 // 為了保證名稱的唯一性,可以使用時間戳作為文件名稱 // path.extname() 可以獲取一個文件的后綴名 cb(null, Date.now() + path.extname(flie.originalname)) } }) // multer 方法會返回一個函數(shù),作為中間件 // - 將配置項傳入 multer const upload = multer({ storage }) const one = upload.single('file') // 將返回的函數(shù) one 作為中間件 uploadRouter.post('/', one, (ctx, next) => { // 如果你需要在后面的中間件獲取文件信息,可以使用 ctx.req.file獲取 ctx.body = { flie: ctx.req.file } })
演示效果我這里就不在進行展示了,大家可以自行嘗試
采用那種方式都應該是實際需求為準,沒有定論,大家自行斟酌即可
使用 koa-multer 實現(xiàn)多文件上傳
實現(xiàn)多文件上傳官方文檔中給我們提供了兩個方法,array 和 fields,具體使用方法與區(qū)別看后續(xù)講解
array 的使用
array 方法的使用與單個文件上傳的區(qū)別很小,首先我我們需要將調用的方法從 single
改成 array
,如下:
// - 第一個參數(shù)指定文件名稱 // - 第二個參數(shù)指定文件上傳的數(shù)量 // 其余文件配置項即使用與單文件上傳別無而至 // - 接收函數(shù)變量為了符合語義我這里改為 const more = upload.array('files', 2)
為了方便給大家演示,多文件上傳我更換了一個接口路徑,如下:
uploadRouter.post('/more', more, pictureResize, (ctx, next) => { // 如果是非文件的文本信息,可以從 ctx.req.body 獲取,這里會給大家進行一個展示 // 單文件獲取信息方式是 ctx.req.file,而多文件是 ctx.req.files,需要大家注意一下 ctx.body = { text: ctx.req.body || '', flies: ctx.req.files } })
配置完成后我們使用 postman 模擬請求發(fā)送,如下:
我們先來看一下上傳的文件,是否在 uploads 文件夾中成功上傳,為了方便對比,我已經(jīng)提前將 uploads 文件夾清空,查看一下結果,如下:
在看一下返回給客戶端的信息
當然超過文件限制數(shù)量,會導致報錯,讓程序崩潰,具體的如何處理這個異常,我們后續(xù)會講解到
fields 的使用
fields 的使用方法與 array 大同小異,只是在使用方式上有一些差距,我們先看一下具體上使用的差距,如下:
// 對fields多文件上傳時出現(xiàn)的錯誤進行捕獲 // fields應該是一個對象數(shù)組,并且 name 可以選擇一個 maxCount, // - name 即文件的字段名 // - maxCount 即指定這個子段的文件可以上傳的數(shù)量 const moreFields = upload.fields([ { name: 'avatar', maxCount: 1 }, { name: 'files', maxCount: 2 } ])
還是為了進行演示,我新建了一個接口路徑,如下:
// 請求路徑修改為 uploadRouter.post('/fields', moreFields, (ctx, next) => { ctx.body = { text: ctx.req.body || '', flies: ctx.req.files } })
使用 postman 發(fā)送請求,如下:
還是清空了 uploads 文件夾,方便我們查看,如下:
array 與 fields 的區(qū)別
相信經(jīng)過上面兩個案例的對比,大家對兩者的區(qū)別已經(jīng)知道了,兩者的區(qū)別在于:array 方法接收的多個文件必須是同一字段名稱,fields 是可以對多個字段名稱進行獲取的
,一般情況下推薦大家使用 arrary 方法,別問,問就是方便好用
對文件進行限制-limits
什么是對文件的限制,比如限制一下當前文件的上傳的大小,數(shù)量等等,還是一樣我給大家從官方上帶來了 limits
的屬性解析,如下:
鑰匙 | 描述 | 默認 |
---|---|---|
fieldNameSize | 最大字段名稱大小 | 100 字節(jié) |
fieldSize | 最大字段值大小(以字節(jié)為單位) | 1MB |
fields | 非文件字段的最大數(shù)量 | 無窮 |
fileSize | 對于多部分表單,最大文件大?。ㄒ宰止?jié)為單位) | 無窮 |
files | 對于多部分表單,文件字段的最大數(shù)量 | 無窮 |
parts | 對于多部分表單,部分的最大數(shù)量(字段 + 文件) | 無窮 |
headerPairs | 對于多部分表單,要解析的標題鍵=>值對的最大數(shù)量 | 2000 |
我給大家解析一下非文件字段,一般表單提交的時候,我們只有 文本和文件 的區(qū)別,非文件指的就是文本數(shù)據(jù),這里的文本數(shù)據(jù)可不是 txt 文件結尾的,就如上述提交中我們提交的文本數(shù)據(jù),即下圖中橙色框中部分,如下:
那我現(xiàn)在介紹一下 limits 的使用,如下:
// 限制上傳的文件 // tips:使用的如果是 single 方法則只能發(fā)送 1 個文件,否則會報錯:Unexpected field,所以單個文件時不用添加 files 限制單次文件數(shù)量 // 這里附上單位轉換 // B(Byte) 1Byte=8bit // KB 1KB=1024B // MB 1MB=1024KB // GB 1GB=1024MB // TB 1TB=1024GB const limits = { fields: 10, fileSize: 104857600, files: 5 } // 將此項傳入 multer const upload = multer({ storage, limits })
至于效果演示我這里就不進行演示了,屬性比較多,大家有興趣可以自行演練
過濾文件-fileFilter
文件過濾指的是排除一些指定類型的文件,為了安全考慮,我們通常會將可執(zhí)行文件排除,不過這里只是作于演示,我就采用排除 png 格式的文件
fileFiter 為我們提供了三個回調函數(shù),分別是:
- cb(null, true):放行文件
- cb(null, false):禁止文件
- cb(new Error(‘png格式文件不允許上傳~’)):拋出一個錯誤
fileFiter 的使用如下:
const fileFilter = (req, file, cb) => { // 獲取文件類型后綴名 const type = path.extname(file.originalname) // 排除 png 格式的圖片 if (type !== '.png') { cb(null, true) } else { cb(null, false) } } // 將此項傳入 multer const upload = multer({fileFilter, storage, limits })
按照上述的例子就可以對文件類型進行過濾了,我們先來看在 postman 中發(fā)送請求,如下:
我們看一下 uploads 文件下的內容,如下:
可以看到 png 的格式是沒有上傳的,我們再看一下返回給客戶端的結果,如下:
是不是發(fā)現(xiàn)好像這樣有一些不友好,并不清楚那個文件沒有符合格式,沒有上傳成功,怎么實現(xiàn)這個功能暫且先放下,稍后在講,我們先看一下第三個回調函數(shù),修改一下 fileFilter 的配置項,如下:
const fileFilter = (req, file, cb) => { // 獲取文件類型后綴名 const type = path.extname(file.originalname) // 排除 png 格式的圖片 if (type !== '.png') { cb(null, true) } else { // 在此處拋出一個錯誤 cb(new Error('png格式文件不允許上傳~')) } }
postmon 模擬的請求不變,我們在看一下執(zhí)行結果,看看 uploads 文件夾下是否多出文件,如下:
可以看到圖片依然沒有增加,證明此次因為一個文件的不符合,導致其他符合的文件也沒有上傳,這樣一看好像也是不太符合我們的需求,在解決之前我們看一下返回給客戶端的結果,如下:
這樣雖然有了提示,但是就沒有實現(xiàn)符合的文件成功存儲的功能,關于這一點我在官網(wǎng)中也沒有找到合適的例子,所以我采用了一個掛載變量的方法來實現(xiàn)這個效果,如下:
const path = require('path') const Koa = require('koa') const Router = require('koa-router') const multer = require('koa-multer') const Jimp = require('jimp') const app = new Koa() // 在頂部注冊一個普通的中間件 // - 在這里注冊中間件會讓任何請求都會觸發(fā)這個中間件,原因的話我這里就不贅述了大家可以去學習一個相關的知識 // - 當然可以只注冊在需要處理文件上傳的接口處,具體位置自己決定即可 app.use(async (ctx, next)=>{ // 通過給 ctx.req 屬性上掛在一個 錯誤文件的 屬性,賦值為空數(shù)組,來記錄本次上傳失敗的文件名稱 ctx.req.failFiles = [] await next() }) // storage limits 等配置項我這里就不在書寫了,大家自行補充即可,fileFilter 配置項需要更改 const fileFilter = (req, file, cb) => { const type = path.extname(file.originalname) if (type !== '.png') { cb(null, true) } else { // 如果文件類型不符合就會經(jīng)過這里,所以可以在這里將失敗的文件名稱存儲進 failFiles 數(shù)組 req.failFiles.push(file.originalname) cb(null, false) } } const upload = multer({fileFilter, storage, limits }) const uploadRouter = new Router({ prefix: '/upload' }) uploadRouter.post('/more', captures, (ctx, next) => { ctx.body = { // 通過讀取 failFiles 數(shù)組,即可展示是失敗的文件 fail: `${ctx.req.failFiles}等文件上傳失敗`, flies: ctx.req.files } }) app.use(uploadRouter.routes()) app.listen(8001, () => { console.log('koa server start success~') })
我們發(fā)送請求看一下 uploads 文件下是否會增加新兩個文件,如下:
可以看到,符合類型的文件存儲成功,在看一下返回給客戶端的信息,如下:
現(xiàn)在就可以完美實現(xiàn)我們的需求,當然三種方式需要結合具體業(yè)務場景使用,如果大家發(fā)現(xiàn)有更好的方式可以一起談論
使用 jimp 對文件進行不同大小的裁剪
為什么需要進行不同大小的裁剪呢,在真實的開發(fā)中,通常有些時候我們只需要展示一個預覽圖,也就是一張比較小的圖片,而不需要展示一張原圖,當然這個圖片的展示可以由前端通過樣式限制,但是如果每次都返回一張原圖,那么請求壓力無疑會大一些,所以可以在上傳時就進行不同大小的裁剪,在合適的請求返回對應的圖片
這個庫的使用方式如下:
// 獲取圖片不同大小 const pictureResize = async (ctx, next) => { // 獲取所有圖像信息 const files = ctx.req.files // 通過 sharp/jimp 庫對圖片進行裁剪 for (const item of files) { // 通過 Jimp.read(路徑) 讀取文件信息,并返回一個對象 // - 如果不希望使用同步的方法等待圖片處理,就可以使用 then 方法異步處理,處理圖片的時間還是比較長的 // 拼接寫入路徑 // 獲取文件類型后綴 const type = path.extname(item.filename) const filename = item.filename.replace(type, '') const url = path.join(item.destination, filename) Jimp.read(item.path).then(res => { // 通過 resize 方法裁剪:參數(shù)一設置寬度,參數(shù)二:讓高度隨高度按比例裁剪 // 在通過 write 方法寫入,傳入寫入路徑即可 // - 并拼接后綴 large(大圖) middle(中等圖片) small(小圖) res.resize(1280, Jimp.AUTO).write(`${url}-large${type}`) res.resize(640, Jimp.AUTO).write(`${url}-middle${type}`) res.resize(320, Jimp.AUTO).write(`${url}-small${type}`) }) } await next() } // 在接口處添加使用即可
來看一下 uploads 問價下是否存儲了 原圖,大小中圖等不同的圖片,我在前端發(fā)送了兩張圖片,所以會有八張圖片,如下:
我們將圖片打開對比看一下:
文件上傳錯誤捕獲
- 關于這個錯誤捕獲官網(wǎng)中給出的例子是給 express 框架的,且是在 multer 的庫中,那難道就沒有辦法可以捕獲這個錯誤了嗎,當然老生常談的 trycatch 這里就不在討論,在 koa 框架中,中間件返回的是一個 promise 對象,所以 koa-multer 這個庫自然也不能例外,返回的函數(shù)是 promise 類型
- 所以根據(jù)這一點,我們可以單獨的對這個方法進行錯誤捕獲,如下:
const captures = async (ctx, next) => { // 文件上傳錯誤捕獲 // 通過使用 then 和 catch 方法進行捕獲,不理解的可以去補充一個 promise 相關的知識 // - 限制文件上傳為 1 let err = await upload .array('files', 2)(ctx, next) .then(res => res) .catch(err => err) if (err) { ctx.body = { msg: err.message } } } // 當然,接口中的函數(shù)也需要更換 uploadRouter.post('/more', captures, (ctx, next) => { ctx.body = { fail: `${ctx.req.failFiles}等文件上傳失敗`, flies: ctx.req.files } })
我們測試一下,當限制數(shù)量為 2,上傳 3 個文件時的結果,看一下返回給客戶端的結果
再看一下后端程序有沒有崩潰
此方法是并沒有在官方文檔中給出,所以具體實現(xiàn)是否一定穩(wěn)定,還有待考究,如果大家有更好的方法,歡迎討論
源碼展示
當然,可能演示時為了區(qū)別,和上面實例代碼會有一些變量名的差別,但是邏輯是一樣的,大家放心閱讀
// 引入內置模塊 const path = require('path') // 引入第三方庫 const Koa = require('koa') const Router = require('koa-router') const multer = require('koa-multer') const Jimp = require('jimp') // 創(chuàng)建app實例 const app = new Koa() app.use(async (ctx, next)=>{ ctx.req.failFiles = [] await next() }) // 創(chuàng)建路由實例 const uploadRouter = new Router({ prefix: '/upload' }) // 文件配置 const storage = multer.diskStorage({ // 存儲路徑 destination: (req, flie, cb) => { cb(null, './uploads') }, // 設置文件名 filename: (req, flie, cb) => { cb(null, Date.now() + path.extname(flie.originalname)) } }) // 限制上傳的文件 const limits = { fields: 10, fileSize: 104857600, files: 5 } // 對上傳的文件進行過濾 const fileFilter = (req, file, cb) => { const type = path.extname(file.originalname) if (type !== '.png') { cb(null, true) } else { req.failFiles.push(file.originalname) cb(null, false) } } // multer 方法會返回一個函數(shù),作為中間件 const upload = multer({ fileFilter, storage, limits }) // 對單文件上傳時出現(xiàn)的錯誤進行捕獲 const capture = async (ctx, next) => { let err = await upload .single('file')(ctx, next) .then(res => res) .catch(err => err) if (err) { ctx.body = { msg: err.message } } } // 對多文件上傳時出現(xiàn)的錯誤進行捕獲 const captures = async (ctx, next) => { let err = await upload .array('files')(ctx, next) .then(res => res) .catch(err => err) if (err) { ctx.body = { msg: err.message } } } // 對fields多文件上傳時出現(xiàn)的錯誤進行捕獲 const capturesFields = async (ctx, next) => { let err = await upload .fields([ { name: 'avatar', maxCount: 1 }, { name: 'files', maxCount: 3 } ])(ctx, next) .then(res => res) .catch(err => err) if (err) { ctx.body = { msg: err.message } } } // 獲取圖片不同大小 const pictureResize = async (ctx, next) => { const files = ctx.req.files for (const item of files) { const type = path.extname(item.filename) const filename = item.filename.replace(type, '') const url = path.join(item.destination, filename) Jimp.read(item.path).then(res => { res.resize(1280, Jimp.AUTO).write(`${url}-large${type}`) res.resize(640, Jimp.AUTO).write(`${url}-middle${type}`) res.resize(320, Jimp.AUTO).write(`${url}-small${type}`) }) } await next() } // 單文件上傳接口 uploadRouter.post('/', capture, (ctx, next) => { ctx.body = { flie: ctx.req.file } }) // 多文件上傳接口 uploadRouter.post('/more', captures, pictureResize, (ctx, next) => { ctx.body = { text: ctx.req.body || '', fail: `${ctx.req.failFiles}等文件上傳失敗`, flies: ctx.req.files } }) // 測試多文件上傳接口之 fields uploadRouter.post('/fields', capturesFields, (ctx, next) => { ctx.body = { text: ctx.req.body || '', flies: ctx.req.files } }) // 注冊接口 app.use(uploadRouter.routes()) // 監(jiān)聽 app.listen(8001, () => { console.log('koa server start success~') })
到此這篇關于在 node 中使用 koa-multer 庫上傳文件詳解的文章就介紹到這了,更多相關node使用 koa-multer 庫上傳文件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Node.js中的npm單獨與批量升級依賴包的方式超詳細講解
npm outdated僅檢查所有已安裝包的依賴關系,并將當前版本遠程倉庫中的最新版本進行對比,不會升級,這篇文章主要介紹了Node.js中的npm單獨與批量升級依賴包的方式超詳細講解,需要的朋友可以參考下2024-02-02Node.js API詳解之 string_decoder用法實例分析
這篇文章主要介紹了Node.js API詳解之 string_decoder用法,結合實例形式分析了Node.js API中string_decoder的功能、用法及操作注意事項,需要的朋友可以參考下2020-04-04node.js express JWT token生成與校驗的實現(xiàn)
本文主要介紹了node.js express JWT token生成與校驗的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-12-12node實現(xiàn)批量上傳本地圖片轉為圖片CDN的項目實踐
本文主要介紹了node實現(xiàn)批量上傳本地圖片轉為圖片CDN的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07Ubuntu 16.04 64位中搭建Node.js開發(fā)環(huán)境教程
如果想要在Ubuntu 16.04上安裝Node.js的話,這篇文章對你來說肯定很重要。Node.js從本質上來說就是一個運行在服務端上的封裝好了輸入輸出流的javascript程序。本文給大家詳細介紹了在Ubuntu 16.04 64位搭建Node.js開發(fā)環(huán)境的步驟,有需要的朋友們可以參考學習。2016-10-10