前端
大緻流程如下
我們使用vue提供的ref屬性來獲得<input type=“file”>标簽的dom對象
該dom對象的files屬性資料類型為FileList,為input的檔案資訊
周遊FileList,得到File對象,使用FileReader讀取File對象,獲得該檔案的ArrayBuffer對象。 (這裡也可以使用FileReader.readAsDataURL(),将該檔案轉化base64編碼的url格式的字元串,然後執行下面步驟傳給後端)
把ArrayBuffer對象轉換為數組,使用post請求發送給後端。(題外話:後端拿到ArrayBuffer對象也能儲存檔案,但很怪的一點是,如果這裡把ArrayBuffer對象直接塞到post參數裡,後端會收到一個空對象,但很明顯ArrayBuffer并不是一個空對象。是以這裡我先把他轉換為數組,然後把數組送到後端。)
<template>
<label><input type="file" ref="input" multiple>檔案</label>
<button @click="submit">送出檔案</button>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import axios from 'axios'
const input = ref<any>()
function submit(){
const files = input.value.files as FileList
for(let i = 0; i < files.length; i++){
const file = files[i]
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
//檔案讀取完畢觸發onload事件
fileReader.onload = () => {
//把ArrayBuffer對象轉換為數組
const arrayBuffer = Array.from(new Uint8Array(fileReader.result as ArrayBuffer))
axios.post('http://localhost:9000/submitFile', {fileName:file.name, arrayBuffer:arrayBuffer})
.then(() => {console.log(file.name + '上傳成功')})
}
}
}
</script>
服務端
服務端流程就比較簡單了,值得注意的有兩點,一是解析body的中間件body-parser預設有大小限制100kb,當大小超過100kb就會報錯,我們要手動去設定這個上限;二是注意fs.writeFile()的使用,它的第二個參數接受一個Buffer資料類型,注意Buffer.from()的用法,他的參數是字元串,緩沖區,數組或arrayBuffer。
const express = require('express')
const cors = require('cors')
const fs = require('fs')
const bodyParser = require('body-parser')
const app = express()
app.use(cors()) //允許簡單請求和非簡單請求的跨域
//解析Content-Type:application/x-www-form-urlencoded的body
app.use(bodyParser.urlencoded({
extended: false,
limit: '50mb' //body大小限制值,預設是100kb,但很多檔案都大于100kb是以需要手動修改
}))
//解析Content-Type:application/json的body
app.use(bodyParser.json({limit: '50mb' })) //同理提高大小限制
const basePath = './' //規定儲存檔案的路徑
app.post('/submitFile', (req, res) => {
//把接收到的内容寫入檔案,Buffer.from()接收一個數組作為參數
fs.writeFile(basePath + req.body.fileName, Buffer.from(req.body.arrayBuffer), error => {
console.log(error)
})
})
app.listen(9000)
更規範化的檔案上傳
上面的方式是自己瞎摸索的,本質上就是通過file對象讀取檔案資料,然後把檔案的資料轉換成數組或者字元串,最後通過post請求傳過去。
我網上搜了下很少有人那樣傳檔案,而且如果使用第三方雲端存儲庫的話,比如網上的圖床和對象存儲雲服務,它們提供的api也不支援那樣的傳輸方式。
其實更規範的上傳檔案方式應該是把file對象直接作為post請求參數的值,然後直接将檔案傳遞給後端,不需要對檔案的資料進行手動讀取或處理。
但之前在嘗試的時候,通過浏覽器抓包發現,請求參數裡的file對象總是一個空對象,之是以會出現這種現象,是因為請求頭設定不正确,當上傳檔案時,我們要把請求頭設定為multipart/form-data,這樣才能正确上傳檔案的資料。
前端使用Content-Type=multipart/form-data上傳檔案
除了請求頭不同之外,和其他的普通請求步驟完全相同。我們在上傳檔案的同時,也可以上傳普通類型的參數,比如下面這個例子,參數中的fieldname是檔案類型,name是字元串類型。
//file是一個File對象
//name是一個字元串
//"fieldname"須和後端multer裡的參數保持一緻
axios.post('/upload', {"fieldname":file, "name":name})
後端express使用中間件multer
multer基本使用方法如下:
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors()) //允許簡單請求和非簡單請求的跨域
//引入multer
const multer = require("multer");
//在storage裡定義檔案的儲存路徑file.destination和檔案名file.filename
//這裡的req.body還未解析,為空。但req.query參數已解析,可用。這裡的req.file為undefined
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// console.log(req.body)
//cb函數設定儲存路徑,__dirname為目前檔案所在目錄
cb(null, __dirname)
},
filename: function (req, file, cb) {
// console.log(req.body)
//獲得檔案名最後一個小數點的位置
let indexOfPoint = file.originalname.lastIndexOf('.')
//獲得檔案擴充名
let fileExtention = file.originalname.slice(indexOfPoint)
if(indexOfPoint === -1){
fileExtention = ''
}
//檔案名 = 時間戳 + 擴充名
let filename = new Date().getTime() + fileExtention
//cb函數設定filename
cb(null, filename)
}
})
// 建立multer執行個體對象
const upload = multer({ storage: storage});
//upload.single()參數是請求裡面二進制對應的屬性名,須和前端保持一緻
app.post('/submitFile', upload.single("fieldname"), (req, res) => {
//檔案儲存操作由multer在背景自動執行,我們無需關心。
//在這裡一般是向前端傳回檔案的儲存資訊,如檔案名和檔案儲存位置,檔案的url路徑等。
//file.originalname指檔案的原始名字,file.filename是我們在上面storage中指定的名字
res.end(JSON.stringify({name: req.file.filename, path: req.file.path}))
})
app.listen(9000)