天天看點

使用vue+axios上傳檔案,服務端使用express+fs接收并儲存檔案

前端

大緻流程如下

我們使用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)
           

繼續閱讀