利用Vue3+Nodejs實(shí)現(xiàn)文件上傳入門篇
前言
最近在復(fù)習(xí)八股, 內(nèi)容挺多的, 但是只看不練也沒辦法真正體會到八股的魅力, 今天來實(shí)現(xiàn)一個簡易版的文件上傳, 可能開始會比較粗糙簡單, 但是里面的總結(jié)都是實(shí)踐出來的, 讓你深入體會到前后端是如何協(xié)作的
1.1 核心概念
什么是 multipart/form-data?`
當(dāng)我們上傳文件時,HTTP 請求需要使用 multipart/form-data 編碼方式。這種方式可以將文件數(shù)據(jù)和普通表單字段一起發(fā)送。
請求標(biāo)頭的Content-Type字段如圖所示:

1.2 前端實(shí)現(xiàn)
在前端 JavaScript 中,我們利用 FormData 來包裝文件進(jìn)行參數(shù)傳輸, FormData 不是一個普通的對象, 而是瀏覽器提供的一個Web API, 當(dāng)服務(wù)端識別到前端傳了這個對象的時候, 就會生成boundary分界線, 并且動態(tài)生成一串隨機(jī)性極高的字符串
在發(fā)送請求時,不要手動設(shè)置請求頭。 瀏覽器檢測到 FormData 實(shí)例后,會自動配置正確的 Content-Type 并附帶必要的 boundary 參數(shù)。
/**
* 基礎(chǔ)文件上傳
*/
const uploadFile = async () => {
if (!selectedFile.value) {
message.value = '請先選擇文件'
return
}
const formData = new FormData()
formData.append('file', selectedFile.value)
try {
message.value = '正在上傳...'
const res = await axios.post('http://localhost:3000/upload', formData)
console.log(res.data)
message.value = `上傳成功: ${res.data.filename}`
} catch (error) {
console.log(error)
message.value = '上傳失敗'
}
}
重要提示:文件上傳最好使用使用 axios 這個第三方庫, 因?yàn)?axios 目前的技術(shù)在文件上傳上很成熟, 可以解決請求頭的配置并且可以不傳formData對象就可以實(shí)現(xiàn)文件上傳, fetch雖然是瀏覽器自帶的API, 但是目前還是有部分瑕疵, 如果你在上傳文件的時候特意配置了請求頭multipart/form-data, 你們?yōu)g覽器就會以為開發(fā)者已經(jīng)解決了文件上傳的問題, 就不會主動拼接 boundary
1.3 后端實(shí)現(xiàn)
原生的nodejs接收前端傳來的文件是以二進(jìn)制流的形式進(jìn)行的, 直接處理流是非常復(fù)雜的,通常引入中間件 multer 來處理, multer可以自動匹配boundary, 不需要通過正則去匹配, 可能會導(dǎo)致文件損壞, multer可以將二進(jìn)制文件和普通數(shù)據(jù)進(jìn)行分離, 有利于后端對數(shù)據(jù)進(jìn)行存儲和修改
multer最早提供了一個配置簡單的dest, 但是它帶來了三個痛點(diǎn)
- 文件名無后綴并且文件名是隨機(jī)生成的哈希值根本看不懂分不清, 并且儲存的數(shù)據(jù)也不能直接打開, 亂碼
- 只能存儲在同一個文件夾下
- http協(xié)議默認(rèn)使用Latin1解析, 導(dǎo)致文件名變成了???
multer.diskStorage是一個存儲引擎, 它提供了兩個核心的回調(diào)函數(shù), 讓你在文件寫入硬盤的最后時刻進(jìn)行攔截和修改
- destination, 控制文件的去向, 可以自定義將文件分開排放
- filename, 提取文件的后綴名并且拼接, 利用buffer修復(fù)亂碼, 并且可以使用隨機(jī)字符串來方式同文件覆蓋
const express = require("express");
const app = express();
const cors = require("cors");
// 引入multer
const multer = require("multer");
const fs = require("fs");
const path = require("path");
// 修復(fù)cors中間件錯誤
app.use(cors());
// 文件保存失敗
const uploadDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
// 這種方式只能仿覆蓋,文件后綴名丟失,無法修復(fù)亂碼問題
// const upload = multer({
// dest: uploadDir,
// });
// 配置 Multer 存儲引擎
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir);
},
filename: function (req, file, cb) {
// 修復(fù)文件名亂碼問題
// Multer 處理 header 時默認(rèn)用 latin1,需轉(zhuǎn)回 utf8
const originalName = Buffer.from(file.originalname, "latin1").toString(
"utf8",
);
// 加上時間戳防止同名文件覆蓋
cb(null, Date.now() + "-" + originalName);
},
});
// 這種方式能仿覆蓋,文件后綴名完整,修復(fù)亂碼問題
const upload = multer({ storage: storage });
app.post("/upload", upload.single("file"), (req, res) => {
res.json({
msg: "上傳成功",
filename: req.file.filename,
});
});
app.listen(3000, () => {
console.log("Server started on port 3000");
});
// 設(shè)置超時時間
server.timeout = 600000;
通過 multer 和 multer.diskStorage 進(jìn)行配置存儲引擎可以實(shí)現(xiàn)文件名保存,同一個文件的上傳,區(qū)別如下


1.4 Vue 組件示例
在視圖層,我們需要處理文件選擇、上傳狀態(tài)(Loading)以及結(jié)果展示三個核心邏輯。
這段代碼展示了如何通過 ref 獲取 DOM 元素,并在異步操作期間鎖定按鈕狀態(tài),防止用戶重復(fù)提交:
<script setup lang="ts">
import axios from 'axios'
import { ref } from 'vue'
const selectedFile = ref(null)
const message = ref('')
const uploadFileChange = (e) => {
selectedFile.value = e.target.files[0]
message.value = ''
}
const uploadFile = async () => {
if (!selectedFile.value) {
message.value = '請先選擇文件'
return
}
const formData = new FormData()
formData.append('file', selectedFile.value)
try {
message.value = '正在上傳...'
const res = await axios.post('http://localhost:3000/upload', formData)
console.log(res.data)
message.value = `上傳成功: ${res.data.filename}`
} catch (error) {
console.log(error)
message.value = '上傳失敗'
}
}
</script>
<template>
<div>
<input type="file" @change="uploadFileChange" />
<button @click="uploadFile">文件上傳</button>
<p v-if="message">{{ message }}</p>
</div>
</template>
<style scoped>
div {
cursor: pointer;
}
</style>
1.5 常見 Bug 與解決方案
| Bug 現(xiàn)象 | 原因分析 | 解決方案 |
|---|---|---|
| 請求體為空,后端收不到文件 | 手動設(shè)置了 Content-Type 請求頭 | 刪除手動設(shè)置的 Content-Type,讓瀏覽器自動處理 |
| 大文件上傳超時 | 默認(rèn)超時時間太短 | 配置更長的超時時間,或使用分片上傳 |
| CORS 跨域錯誤 | 前后端不同源 | 后端配置 CORS 中間件 app.use(cors()) |
總結(jié)
到此這篇關(guān)于利用Vue3+Nodejs實(shí)現(xiàn)文件上傳入門篇的文章就介紹到這了,更多相關(guān)Vue3+Nodejs文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue深拷貝的3種實(shí)現(xiàn)方式小結(jié)
當(dāng)使用同一個對象產(chǎn)生沖突時,可以使用lodash包,對該對象進(jìn)行深拷貝,從而使操作的對象為不同的對象,這篇文章主要給大家介紹了關(guān)于vue深拷貝的3種實(shí)現(xiàn)方式,需要的朋友可以參考下2023-02-02
vue-router之nuxt動態(tài)路由設(shè)置的兩種方法小結(jié)
今天小編就為大家分享一篇vue-router之nuxt動態(tài)路由設(shè)置的兩種方法小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
基于Vue3實(shí)現(xiàn)數(shù)字華容道游戲的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Vue編寫一個數(shù)字華容道游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04
el-table?動態(tài)合并不定項(xiàng)多級表頭的方法
本文主要介紹了el-table?動態(tài)合并不定項(xiàng)多級表頭的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
ElementUI中el-dropdown-item點(diǎn)擊事件無效問題
這篇文章主要介紹了ElementUI中el-dropdown-item點(diǎn)擊事件無效問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04

