⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載javascript
在平常工做中,文件上傳是一個很常見的功能。在項目開發過程當中,咱們一般都會使用一些成熟的上傳組件來實現對應的功能。通常來講,成熟的上傳組件不只會提供漂亮 UI 或好的交互體驗,並且還會提供多種不一樣的上傳方式,以知足不一樣的場景需求。css
通常在咱們工做中,主要會涉及到 8 種文件上傳的場景,每一種場景背後都使用不一樣的技術,其中也有不少細節須要咱們額外注意。今天阿寶哥就來帶你們總結一下這 8 種場景,讓你們能更好地理解成熟上傳組件所提供的功能。閱讀本文後,你將會了解如下的內容:html
單文件上傳:利用 input
元素的 accept
屬性限制上傳文件的類型、利用 JS 檢測文件的類型及使用 Koa 實現單文件上傳的功能;前端
多文件上傳:利用 input
元素的 multiple
屬性支持選擇多文件及使用 Koa 實現多文件上傳的功能;java
目錄上傳:利用 input
元素上的 webkitdirectory
屬性支持目錄上傳的功能及使用 Koa 實現目錄上傳並按文件目錄結構存放的功能;node
壓縮目錄上傳:在目錄上傳的基礎上,利用 JSZip 實現壓縮目錄上傳的功能;ios
拖拽上傳:利用拖拽事件和 DataTransfer 對象實現拖拽上傳的功能;git
剪貼板上傳:利用剪貼板事件和 Clipboard API 實現剪貼板上傳的功能;github
大文件分塊上傳:利用 Blob.slice、SparkMD5 和第三方庫 async-pool 實現大文件併發上傳的功能;web
服務端上傳:利用第三方庫 form-data 實現服務端文件流式上傳的功能。
對於單文件上傳的場景來講,最多見的是圖片上傳的場景,因此咱們就以圖片上傳爲例,先來介紹單文件上傳的基本流程。
html
在如下代碼中,咱們經過 input
元素的 accept
屬性限制了上傳文件的類型。這裏使用 image/*
限制只能選擇圖片文件,固然你也能夠設置特定的類型,好比 image/png
或 image/png,image/jpeg
。
<input id="uploadFile" type="file" accept="image/*" />
<button id="submit" onclick="uploadFile()">上傳文件</button>
複製代碼
須要注意的是,雖然咱們把 input 元素的 accept
屬性設置爲 image/png
。但若是用戶把 jpg/jpeg
格式的圖片後綴名改成 .png
,就能夠成功繞過這個限制。要解決這個問題,咱們能夠經過讀取文件中的二進制數據來識別正確的文件類型。
要查看圖片對應的二進制數據,咱們能夠藉助一些現成的編輯器,好比 Windows 平臺下的 WinHex 或 macOS 平臺下的 Synalyze It! Pro 十六進制編輯器。這裏咱們使用 Synalyze It! Pro 這個編輯器,來查看阿寶哥頭像對應的二進制數據。
那麼在前端可否不借助工具,讀取文件的二進制數據呢?答案是能夠的,這裏阿寶哥就不展開介紹了。感興趣的話,你能夠閱讀 JavaScript 如何檢測文件的類型? 這篇文章。另外,須要注意的是 input
元素 accept
屬性有存在兼容性問題。好比 IE 9 如下不支持,具體以下圖所示:
(圖片來源 —— caniuse.com/input-file-…
js
const uploadFileEle = document.querySelector("#uploadFile");
const request = axios.create({
baseURL: "http://localhost:3000/upload",
timeout: 60000,
});
async function uploadFile() {
if (!uploadFileEle.files.length) return;
const file = uploadFileEle.files[0]; // 獲取單個文件
// 省略文件的校驗過程,好比文件類型、大小校驗
upload({
url: "/single",
file,
});
}
function upload({ url, file, fieldName = "file" }) {
let formData = new FormData();
formData.set(fieldName, file);
request.post(url, formData, {
// 監聽上傳進度
onUploadProgress: function (progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(percentCompleted);
},
});
}
複製代碼
在以上代碼中,咱們先把讀取的 File 對象封裝成 FormData 對象,而後利用 Axios 實例的 post
方法實現文件上傳的功能。 在上傳前,經過設置請求配置對象的 onUploadProgress
屬性,就能夠獲取文件的上傳進度。
Koa 是一個簡單易用的 Web 框架,它的特色是優雅、簡潔、輕量、自由度高。因此咱們選擇它來搭建文件服務,並使用如下中間件來實現相應的功能:
multipart/form-data
的中間件;const path = require("path");
const Koa = require("koa");
const serve = require("koa-static");
const cors = require("@koa/cors");
const multer = require("@koa/multer");
const Router = require("@koa/router");
const app = new Koa();
const router = new Router();
const PORT = 3000;
// 上傳後資源的URL地址
const RESOURCE_URL = `http://localhost:${PORT}`;
// 存儲上傳文件的目錄
const UPLOAD_DIR = path.join(__dirname, "/public/upload");
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
// 設置文件的存儲目錄
cb(null, UPLOAD_DIR);
},
filename: function (req, file, cb) {
// 設置文件名
cb(null, `${file.originalname}`);
},
});
const multerUpload = multer({ storage });
router.get("/", async (ctx) => {
ctx.body = "歡迎使用文件服務(by 阿寶哥)";
});
router.post(
"/upload/single",
async (ctx, next) => {
try {
await next();
ctx.body = {
code: 1,
msg: "文件上傳成功",
url: `${RESOURCE_URL}/${ctx.file.originalname}`,
};
} catch (error) {
ctx.body = {
code: 0,
msg: "文件上傳失敗"
};
}
},
multerUpload.single("file")
);
// 註冊中間件
app.use(cors());
app.use(serve(UPLOAD_DIR));
app.use(router.routes()).use(router.allowedMethods());
app.listen(PORT, () => {
console.log(`app starting at port ${PORT}`);
});
複製代碼
以上代碼相對比較簡單,咱們就不展開介紹了。Koa 內核很簡潔,擴展功能都是經過中間件來實現。好比示例中使用到的路由、CORS、靜態資源處理等功能都是經過中間件實現。所以要想掌握 Koa 這個框架,核心是掌握它的中間件機制。若是你想深刻了解的話,能夠閱讀 如何更好地理解中間件和洋蔥模型 這篇文章。其實除了單文件上傳外,在文件上傳的場景中,咱們也能夠同時上傳多個文件。
單文件上傳示例:single-file-upload
要上傳多個文件,首先咱們須要容許用戶同時選擇多個文件。要實現這個功能,咱們能夠利用 input
元素的 multiple
屬性。跟前面介紹的 accept
屬性同樣,該屬性也存在兼容性問題,具體以下圖所示:
(圖片來源 —— caniuse.com/mdn-api_htm…
html
相比單文件上傳的代碼,多文件上傳場景下的 input
元素多了一個 multiple
屬性:
<input id="uploadFile" type="file" accept="image/*" multiple />
<button id="submit" onclick="uploadFile()">上傳文件</button>
複製代碼
js
在單文件上傳的代碼中,咱們經過 uploadFileEle.files[0]
獲取單個文件,而對於多文件上傳來講,咱們須要獲取已選擇的文件列表,即經過 uploadFileEle.files
來獲取,它返回的是一個 FileList 對象。
async function uploadFile() {
if (!uploadFileEle.files.length) return;
const files = Array.from(uploadFileEle.files);
upload({
url: "/multiple",
files,
});
}
複製代碼
由於要支持上傳多個文件,因此咱們須要同步更新一下 upload
函數。對應的處理邏輯就是遍歷文件列表,而後使用 FormData 對象的 append
方法來添加多個文件,具體代碼以下所示:
function upload({ url, files, fieldName = "file" }) {
let formData = new FormData();
files.forEach((file) => {
formData.append(fieldName, file);
});
request.post(url, formData, {
// 監聽上傳進度
onUploadProgress: function (progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(percentCompleted);
},
});
}
複製代碼
在如下代碼中,咱們定義了一個新的路由 —— /upload/multiple
來處理多文件上傳的功能。當全部文件都成功上傳後,就會返回一個已上傳文件的 url 地址列表:
router.post(
"/upload/multiple",
async (ctx, next) => {
try {
await next();
urls = ctx.files.file.map(file => `${RESOURCE_URL}/${file.originalname}`);
ctx.body = {
code: 1,
msg: "文件上傳成功",
urls
};
} catch (error) {
ctx.body = {
code: 0,
msg: "文件上傳失敗",
};
}
},
multerUpload.fields([
{
name: "file", // 與FormData表單項的fieldName想對應
},
])
);
複製代碼
介紹完單文件和多文件上傳的功能,接下來咱們來介紹目錄上傳的功能。
多文件上傳示例:multiple-file-upload
可能你還不知道,input
元素上還有一個的 webkitdirectory
屬性。當設置了 webkitdirectory
屬性以後,咱們就能夠選擇目錄了。
<input id="uploadFile" type="file" accept="image/*" webkitdirectory />
複製代碼
當咱們選擇了指定目錄以後,好比阿寶哥桌面上的 images
目錄,就會顯示如下確認框:
點擊上傳按鈕以後,咱們就能夠獲取文件列表。列表中的文件對象上含有一個 webkitRelativePath
屬性,用於表示當前文件的相對路徑。
雖然經過 webkitdirectory
屬性能夠很容易地實現選擇目錄的功能,但在實際項目中咱們還須要考慮它的兼容性。好比在 IE 11 如下的版本就不支持該屬性,其它瀏覽器的兼容性以下圖所示:
(圖片來源 —— caniuse.com/?search=web…
瞭解完 webkitdirectory
屬性的兼容性,咱們先來介紹前端的實現代碼。
爲了讓服務端能按照實際的目錄結構來存放對應的文件,在添加表單項時咱們須要把當前文件的路徑提交到服務端。此外,爲了確保@koa/multer
能正確處理文件的路徑,咱們須要對路徑進行特殊處理。即把 /
斜槓替換爲 @
符號。對應的處理方式以下所示:
function upload({ url, files, fieldName = "file" }) {
let formData = new FormData();
files.forEach((file, i) => {
formData.append(
fieldName,
files[i],
files[i].webkitRelativePath.replace(/\//g, "@");
);
});
request.post(url, formData); // 省略上傳進度處理
}
複製代碼
目錄上傳與多文件上傳,服務端代碼的主要區別就是 @koa/multer
中間件的配置對象不同。在 destination
屬性對應的函數中,咱們須要把文件名中 @
還原成 /
,而後根據文件的實際路徑來生成目錄。
const fse = require("fs-extra");
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
// images@image-1.jpeg => images/image-1.jpeg
let relativePath = file.originalname.replace(/@/g, path.sep);
let index = relativePath.lastIndexOf(path.sep);
let fileDir = path.join(UPLOAD_DIR, relativePath.substr(0, index));
// 確保文件目錄存在,若不存在的話,會自動建立
await fse.ensureDir(fileDir);
cb(null, fileDir);
},
filename: function (req, file, cb) {
let parts = file.originalname.split("@");
cb(null, `${parts[parts.length - 1]}`);
},
});
複製代碼
如今咱們已經實現了目錄上傳的功能,那麼可否把目錄下的文件壓縮成一個壓縮包後再上傳呢?答案是能夠的,接下來咱們來介紹如何實現壓縮目錄上傳的功能。
目錄上傳示例:directory-upload
在 JavaScript 如何在線解壓 ZIP 文件? 這篇文章中,介紹了在瀏覽器端如何使用 JSZip 這個庫實如今線解壓 ZIP 文件的功能。 JSZip 這個庫除了能夠解析 ZIP 文件以外,它還能夠用來 建立和編輯 ZIP 文件。利用 JSZip 這個庫提供的 API,咱們就能夠把目錄下的全部文件壓縮成 ZIP 文件,而後再把生成的 ZIP 文件上傳到服務器。
JSZip 實例上的 file(name, data [,options])
方法,能夠把文件添加到 ZIP 文件中。基於該方法咱們能夠封裝了一個 generateZipFile
函數,用於把目錄下的文件列表壓縮成一個 ZIP 文件。如下是 generateZipFile
函數的具體實現:
function generateZipFile( zipName, files, options = { type: "blob", compression: "DEFLATE" } ) {
return new Promise((resolve, reject) => {
const zip = new JSZip();
for (let i = 0; i < files.length; i++) {
zip.file(files[i].webkitRelativePath, files[i]);
}
zip.generateAsync(options).then(function (blob) {
zipName = zipName || Date.now() + ".zip";
const zipFile = new File([blob], zipName, {
type: "application/zip",
});
resolve(zipFile);
});
});
}
複製代碼
在建立完 generateZipFile
函數以後,咱們須要更新一下前面已經介紹過的 uploadFile
函數:
async function uploadFile() {
let fileList = uploadFileEle.files;
if (!fileList.length) return;
let webkitRelativePath = fileList[0].webkitRelativePath;
let zipFileName = webkitRelativePath.split("/")[0] + ".zip";
let zipFile = await generateZipFile(zipFileName, fileList);
upload({
url: "/single",
file: zipFile,
fileName: zipFileName
});
}
複製代碼
在以上的 uploadFile
函數中,咱們會對返回的 FileList 對象進行處理,即調用 generateZipFile
函數來生成 ZIP 文件。此外,爲了在服務端接收壓縮文件時,能獲取到文件名,咱們爲 upload
函數增長了一個 fileName
參數,該參數用於調用 formData.append
方法時,設置上傳文件的文件名:
function upload({ url, file, fileName, fieldName = "file" }) {
if (!url || !file) return;
let formData = new FormData();
formData.append(
fieldName, file, fileName
);
request.post(url, formData); // 省略上傳進度跟蹤
}
複製代碼
以上就是壓縮目錄上傳,前端部分的 JS 代碼,服務端的代碼能夠參考前面單文件上傳的相關代碼。
壓縮目錄上傳示例:directory-compress-upload
要實現拖拽上傳的功能,咱們須要先了解與拖拽相關的事件。好比 drag
、dragend
、dragenter
、dragover
或 drop
事件等。這裏咱們只介紹接下來要用到的拖拽事件:
dragenter
:當拖拽元素或選中的文本到一個可釋放目標時觸發;dragover
:當元素或選中的文本被拖到一個可釋放目標上時觸發(每100毫秒觸發一次);dragleave
:當拖拽元素或選中的文本離開一個可釋放目標時觸發;drop
:當元素或選中的文本在可釋放目標上被釋放時觸發。基於上面的這些事件,咱們就能夠提升用戶拖拽的體驗。好比當用戶拖拽的元素進入目標區域時,對目標區域進行高亮顯示。當用戶拖拽的元素離開目標區域時,移除高亮顯示。很明顯當 drop
事件觸發後,拖拽的元素已經放入目標區域了,這時咱們就須要獲取對應的數據。
那麼如何獲取拖拽對應的數據呢?這時咱們須要使用 DataTransfer 對象,該對象用於保存拖動並放下過程當中的數據。它能夠保存一項或多項數據,這些數據項能夠是一種或者多種數據類型。若拖動操做涉及拖動文件,則咱們能夠經過 DataTransfer 對象的 files
屬性來獲取文件列表。
介紹完拖拽上傳相關的知識後,咱們來看一下具體如何實現拖拽上傳的功能。
html
<div id="dropArea">
<p>拖拽上傳文件</p>
<div id="imagePreview"></div>
</div>
複製代碼
css
#dropArea {
width: 300px;
height: 300px;
border: 1px dashed gray;
margin-bottom: 20px;
}
#dropArea p {
text-align: center;
color: #999;
}
#dropArea.highlighted {
background-color: #ddd;
}
#imagePreview {
max-height: 250px;
overflow-y: scroll;
}
#imagePreview img {
width: 100%;
display: block;
margin: auto;
}
複製代碼
js
爲了讓你們可以更好地閱讀拖拽上傳的相關代碼,咱們把代碼拆成 4 部分來說解:
一、阻止默認拖拽行爲
const dropAreaEle = document.querySelector("#dropArea");
const imgPreviewEle = document.querySelector("#imagePreview");
const IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png)$/i;
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
dropAreaEle.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
複製代碼
二、切換目標區域的高亮狀態
["dragenter", "dragover"].forEach((eventName) => {
dropAreaEle.addEventListener(eventName, highlight, false);
});
["dragleave", "drop"].forEach((eventName) => {
dropAreaEle.addEventListener(eventName, unhighlight, false);
});
// 添加高亮樣式
function highlight(e) {
dropAreaEle.classList.add("highlighted");
}
// 移除高亮樣式
function unhighlight(e) {
dropAreaEle.classList.remove("highlighted");
}
複製代碼
三、處理圖片預覽
dropAreaEle.addEventListener("drop", handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = [...dt.files];
files.forEach((file) => {
previewImage(file, imgPreviewEle);
});
// 省略文件上傳代碼
}
function previewImage(file, container) {
if (IMAGE_MIME_REGEX.test(file.type)) {
const reader = new FileReader();
reader.onload = function (e) {
let img = document.createElement("img");
img.src = e.target.result;
container.append(img);
};
reader.readAsDataURL(file);
}
}
複製代碼
四、文件上傳
function handleDrop(e) {
const dt = e.dataTransfer;
const files = [...dt.files];
// 省略圖片預覽代碼
files.forEach((file) => {
upload({
url: "/single",
file,
});
});
}
const request = axios.create({
baseURL: "http://localhost:3000/upload",
timeout: 60000,
});
function upload({ url, file, fieldName = "file" }) {
let formData = new FormData();
formData.set(fieldName, file);
request.post(url, formData, {
// 監聽上傳進度
onUploadProgress: function (progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(percentCompleted);
},
});
}
複製代碼
拖拽上傳算是一個比較常見的場景,不少成熟的上傳組件都支持該功能。其實除了拖拽上傳外,還能夠利用剪貼板實現複製上傳的功能。
拖拽上傳示例:drag-drop-upload
在介紹如何實現剪貼板上傳的功能前,咱們須要瞭解一下 Clipboard API。Clipboard
接口實現了 Clipboard API,若是用戶授予了相應的權限,就能提供系統剪貼板的讀寫訪問。在 Web 應用程序中,Clipboard API 可用於實現剪切、複製和粘貼功能。該 API 用於取代經過 document.execCommand API 來實現剪貼板的操做。
在實際項目中,咱們不須要手動建立 Clipboard
對象,而是經過 navigator.clipboard
來獲取 Clipboard
對象:
在獲取 Clipboard
對象以後,咱們就能夠利用該對象提供的 API 來訪問剪貼板,好比:
navigator.clipboard.readText().then(
clipText => document.querySelector(".editor").innerText = clipText
);
複製代碼
以上代碼將 HTML 中含有 .editor
類的第一個元素的內容替換爲剪貼板的內容。若是剪貼板爲空,或者不包含任何文本,則元素的內容將被清空。這是由於在剪貼板爲空或者不包含文本時,readText
方法會返回一個空字符串。
利用 Clipboard API 咱們能夠很方便地操做剪貼板,但實際項目使用過程當中也得考慮它的兼容性:
(圖片來源 —— caniuse.com/async-clipb…
要實現剪貼板上傳的功能,能夠分爲如下 3 個步驟:
FormData
對象並上傳。瞭解完上述步驟,接下來咱們來分析一下具體實現的代碼。
html
<div id="uploadArea">
<p>請先複製圖片後再執行粘貼操做</p>
</div>
複製代碼
css
#uploadArea {
width: 400px;
height: 400px;
border: 1px dashed gray;
display: table-cell;
vertical-align: middle;
}
#uploadArea p {
text-align: center;
color: #999;
}
#uploadArea img {
max-width: 100%;
max-height: 100%;
display: block;
margin: auto;
}
複製代碼
js
在如下代碼中,咱們使用 addEventListener
方法爲 uploadArea
容器添加 paste
事件。在對應的事件處理函數中,咱們會優先判斷當前瀏覽器是否支持異步 Clipboard API。若是支持的話,就會經過 navigator.clipboard.read
方法來讀取剪貼板中的內容。在讀取內容以後,咱們會經過正則判斷剪貼板項中是否包含圖片資源,若是有的話會調用 previewImage
方法執行圖片預覽操做並把返回的 blob
對象保存起來,用於後續的上傳操做。
const IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png)$/i;
const uploadAreaEle = document.querySelector("#uploadArea");
uploadAreaEle.addEventListener("paste", async (e) => {
e.preventDefault();
const files = [];
if (navigator.clipboard) {
let clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
for (const type of clipboardItem.types) {
if (IMAGE_MIME_REGEX.test(type)) {
const blob = await clipboardItem.getType(type);
insertImage(blob, uploadAreaEle);
files.push(blob);
}
}
}
} else {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (IMAGE_MIME_REGEX.test(items[i].type)) {
let file = items[i].getAsFile();
insertImage(file, uploadAreaEle);
files.push(file);
}
}
}
if (files.length > 0) {
confirm("剪貼板檢測到圖片文件,是否執行上傳操做?")
&& upload({
url: "/multiple",
files,
});
}
});
複製代碼
若當前瀏覽器不支持異步 Clipboard API,則咱們會嘗試經過 e.clipboardData.items
來訪問剪貼板中的內容。須要注意的是,在遍歷剪貼板內容項的時候,咱們是經過 getAsFile
方法來獲取剪貼板的內容。固然該方法也存在兼容性問題,具體以下圖所示:
(圖片來源 —— caniuse.com/mdn-api_dat…
前面已經提到,當從剪貼板解析到圖片資源時,會讓用戶進行預覽,該功能是基於 FileReader API 來實現的,對應的代碼以下所示:
function previewImage(file, container) {
const reader = new FileReader();
reader.onload = function (e) {
let img = document.createElement("img");
img.src = e.target.result;
container.append(img);
};
reader.readAsDataURL(file);
}
複製代碼
當用戶預覽完成後,若是確認上傳咱們就會執行文件的上傳操做。由於文件是從剪貼板中讀取的,因此在上傳前咱們會根據文件的類型,自動爲它生成一個文件名,具體是採用時間戳加文件後綴的形式:
function upload({ url, files, fieldName = "file" }) {
let formData = new FormData();
files.forEach((file) => {
let fileName = +new Date() + "." + IMAGE_MIME_REGEX.exec(file.type)[1];
formData.append(fieldName, file, fileName);
});
request.post(url, formData);
}
複製代碼
前面咱們已經介紹了文件上傳的多種不一樣場景,接下來咱們來介紹一個 「特殊」 的場景 —— 大文件上傳。
剪貼板上傳示例:clipboard-upload
相信你可能已經瞭解大文件上傳的解決方案,在上傳大文件時,爲了提升上傳的效率,咱們通常會使用 Blob.slice 方法對大文件按照指定的大小進行切割,而後經過多線程進行分塊上傳,等全部分塊都成功上傳後,再通知服務端進行分塊合併。具體處理方案以下圖所示:
由於在 JavaScript 中如何實現大文件併發上傳? 這篇文章中,阿寶哥已經詳細介紹了大文件併發上傳的方案,因此這裏就不展開介紹了。咱們只回顧一下大文件併發上傳的完整流程:
前面咱們都是介紹客戶端文件上傳的場景,其實也有服務端文件上傳的場景。好比在服務端動態生成海報後,上傳到另一臺服務器或雲廠商的 OSS(Object Storage Service)。下面咱們就以 Node.js 爲例來介紹在服務端如何上傳文件。
大文件分塊上傳示例:big-file-upload
服務器上傳就是把文件從一臺服務器上傳到另一臺服務器。藉助 Github 上 form-data 這個庫提供的功能,咱們能夠很容易地實現服務器上傳的功能。下面咱們來簡單介紹一下單文件和多文件上傳的功能:
const fs = require("fs");
const path = require("path");
const FormData = require("form-data");
const form1 = new FormData();
form1.append("file", fs.createReadStream(path.join(__dirname, "images/image-1.jpeg")));
form1.submit("http://localhost:3000/upload/single", (error, response) => {
if(error) {
console.log("單圖上傳失敗");
return;
}
console.log("單圖上傳成功");
});
複製代碼
const form2 = new FormData();
form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-2.jpeg")));
form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-3.jpeg")));
form2.submit("http://localhost:3000/upload/multiple", (error, response) => {
if(error) {
console.log("多圖上傳失敗");
return;
}
console.log("多圖上傳成功");
});
複製代碼
觀察以上代碼可知,建立完 FormData
對象以後,咱們只須要經過 fs.createReadStream
API 建立可讀流,而後調用 FormData
對象的 append
方法添加表單項,最後再調用 submit
方法執行提交操做便可。
其實除了 ReadableStream
以外,FormData
對象的 append
方法還支持如下類型:
const FormData = require('form-data');
const http = require('http');
const form = new FormData();
http.request('http://nodejs.org/images/logo.png', function(response) {
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_logo', response);
});
複製代碼
服務端文件上傳的內容就介紹到這裏,關於 form-data 這個庫的其餘用法,感興趣的話,能夠閱讀對應的使用文檔。其實除了以上介紹的八種場景外,在平常工做中,你也可能會使用一些同步工具,好比 Syncthing 文件同步工具實現文件傳輸。好的,本文的全部內容都已經介紹完了,最後咱們來作一個總結。
服務端上傳示例:server-upload
本文阿寶哥詳細介紹了文件上傳的八種場景,但願閱讀完本文後,你對八種場景背後使用的技術有必定的瞭解。因爲篇幅有限,阿寶哥就沒有展開介紹與 multipart/form-data
類型相關的內容,感興趣的小夥伴能夠自行了解一下。
此外,在實際項目中,你能夠考慮直接使用成熟的第三方組件,好比 Github 上的 Star 數 11K+ 的 filepond。該組件採用插件化的架構,以插件的方式,提供了很是多的功能,好比 File encode、File rename、File poster、Image preview 和 Image crop 等。總之,它是一個很不錯的組件,之後有機會的話,你們能夠嘗試一下。