大檔案上傳是前端開發中常見的需求之一,特別是在需要處理高畫質圖片、影片或其他大型檔案時。最佳化大檔案上傳不僅可以提升使用者體驗,還能有效減輕伺服器負擔。本文將深入探討大檔案上傳的幾種常見最佳化技術,包括檔案切片與併發上傳、斷點續傳、後臺處理最佳化、安全性考慮和使用者體驗最佳化。
1. 前言
在現代Web應用中,使用者上傳大檔案已成為常見需求。然而,直接上傳大檔案會面臨諸多挑戰,例如網路不穩定導致上傳中斷、長時間上傳導致使用者體驗差、伺服器壓力大等。因此,最佳化大檔案上傳效能顯得尤為重要。
2. 檔案切片與併發上傳
2.1 檔案切片原理
檔案切片(Chunking)是將大檔案分成若干小片段,每個片段獨立上傳的方法。這樣做可以有效減少單次上傳的資料量,降低上傳失敗的機率。
2.2 實現步驟
- 前端切片:利用
Blob
物件的slice
方法將檔案切片。 - 併發上傳:使用
Promise.all
實現多個切片併發上傳。 - 合併請求:上傳完成後,通知伺服器合併這些切片。
3. 斷點續傳
斷點續傳(Resumable Uploads)可以在上傳過程中斷時,從斷點繼續上傳,避免重新上傳整個檔案。
3.1 實現步驟
- 前端記錄進度:使用
localStorage
記錄已上傳的切片資訊。 - 斷點續傳:上傳時檢查哪些切片未上傳,繼續上傳未完成的部分。
4. 後臺處理最佳化
4.1 分片接收與合併
伺服器需要支援接收分片請求,並在所有分片上傳完成後合併檔案。可以利用中介軟體或服務端程式語言實現這一邏輯。
5. 安全性考慮
5.1 檔案型別校驗
在前端和後端都應對檔案型別進行校驗,確保上傳的檔案型別符合預期。
5.2 檔案大小限制
限制單個檔案和總上傳檔案的大小,防止惡意使用者上傳過大的檔案造成伺服器壓力。
6. 使用者體驗最佳化
6.1 進度顯示
透過顯示上傳進度條,讓使用者瞭解上傳進度,提升使用者體驗。
6.2 網路波動處理
考慮到使用者可能在網路不穩定的環境中上傳檔案,可以增加失敗重試機制。
完整例項
後端程式碼(Node.js + Express)
安裝依賴
npm init -y
npm install express multer fs
建立伺服器檔案(server.js)
const express = require('express'); const multer = require('multer'); const fs = require('fs'); const path = require('path'); const bodyParser = require('body-parser'); const app = express(); const upload = multer({ dest: 'uploads/' }); app.use(bodyParser.json()); // 路由:處理檔案切片上傳 app.post('/upload', upload.single('chunk'), (req, res) => { const { index, fileName } = req.body; const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${index}`); fs.renameSync(req.file.path, chunkPath); res.status(200).send('Chunk uploaded'); }); // 路由:合併切片 app.post('/merge', (req, res) => { const { totalChunks, fileName } = req.body; const filePath = path.join(__dirname, 'uploads', fileName); const writeStream = fs.createWriteStream(filePath); for (let i = 0; i < totalChunks; i++) { const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${i}`); const data = fs.readFileSync(chunkPath); writeStream.write(data); fs.unlinkSync(chunkPath); } writeStream.end(); res.status(200).send('File merged'); }); app.listen(3000, () => { console.log('Server started on http://localhost:3000'); });
前端程式碼(index.html + script.js)
- 建立HTML檔案(index.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>大檔案上傳</title> </head> <body> <input type="file" id="fileInput"> <progress id="progressBar" value="0" max="100"></progress> <button onclick="uploadFile()">上傳檔案</button> <script src="script.js"></script> </body> </html>
- 建立JavaScript檔案(script.js)
const fileInput = document.getElementById('fileInput'); const progressBar = document.getElementById('progressBar'); const chunkSize = 5 * 1024 * 1024; // 5MB const uploadChunk = async (chunk, index, fileName) => { const formData = new FormData(); formData.append('chunk', chunk); formData.append('index', index); formData.append('fileName', fileName); await fetch('/upload', { method: 'POST', body: formData }); updateProgressBar(index); }; const updateProgressBar = (index) => { const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || []; if (!uploadedChunks.includes(index)) { uploadedChunks.push(index); progressBar.value = (uploadedChunks.length / totalChunks) * 100; localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks)); } }; const uploadFile = async () => { const file = fileInput.files[0]; const totalChunks = Math.ceil(file.size / chunkSize); const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || []; const promises = []; for (let i = 0; i < totalChunks; i++) { if (!uploadedChunks.includes(i)) { const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize); promises.push(uploadChunk(chunk, i, file.name)); } } await Promise.all(promises); await fetch('/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ totalChunks, fileName: file.name }) }); localStorage.removeItem('uploadedChunks'); alert('檔案上傳成功'); };
啟動後端伺服器
- 在瀏覽器中開啟前端頁面
將index.html
檔案在瀏覽器中開啟,選擇檔案並點選“上傳檔案”按鈕即可看到檔案上傳進度。
node server.js