當我們在做文件的導入功能的時候,如果導入的文件過大,可能會導所需要的時間夠長,且失敗后需要重新上傳,我們需要前后端結合的方式解決這個問題
我們需要做幾件事情如下:
倉庫地址
在JavaScript中,文件FIle對象是Blob對象的子類,Blob對象包含一個重要的方法slice通過這個方法,我們就可以對二進制文件進行拆分,具體代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=s, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body>
<input type="file" id="fileInput">
<button id="uploadBtn">上傳</button>
</body>
<script>
// 請求基準地址
axios.defaults.baseURL='http://localhost:3000'
// 選中的文件
var file=null
// 選擇文件
document.getElementById('fileInput').onchange=function({target: {files}}){
file=files[0]
}
// 開始上傳
document.getElementById('uploadBtn').onclick=async function(){
if (!file) return
// 創建切片
// let size=1024 * 1024 * 10 //10MB 切片大小
let size=1024 * 50 //50KB 切片大小
let fileChunks=[]
let index=0 //切片序號
for(let cur=0; cur < file.size; cur +=size){
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size)
})
}
// 上傳切片
const uploadList=fileChunks.map((item, index)=> {
let formData=new FormData()
formData.append('filename', file.name)
formData.append('hash', item.hash)
formData.append('chunk', item.chunk)
return axios({
method: 'post',
url: '/upload',
data: formData
})
})
await Promise.all(uploadList)
// 合并切片
await axios({
method: 'get',
url: '/merge',
params: {
filename: file.name
}
});
console.log('上傳完成')
}
</script>
</html>
結合Promise.race和異步函數實現,多個請求同時并發的數量,防止瀏覽器內存溢出,具體代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=s, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body>
<input type="file" id="fileInput">
<button id="uploadBtn">上傳</button>
</body>
<script>
// 請求基準地址
axios.defaults.baseURL='http://localhost:3000'
// 選中的文件
var file=null
// 選擇文件
document.getElementById('fileInput').onchange=function({target: {files}}){
file=files[0]
}
// 開始上傳
document.getElementById('uploadBtn').onclick=async function(){
if (!file) return
// 創建切片
// let size=1024 * 1024 * 10; //10MB 切片大小
let size=1024 * 50 //50KB 切片大小
let fileChunks=[]
let index=0 //切片序號
for(let cur=0; cur < file.size; cur +=size){
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size)
});
}
// 控制并發
let pool=[]//并發池
let max=3 //最大并發量
for(let i=0;i<fileChunks.length;i++){
let item=fileChunks[i]
let formData=new FormData()
formData.append('filename', file.name)
formData.append('hash', item.hash)
formData.append('chunk', item.chunk)
// 上傳切片
let task=axios({
method: 'post',
url: '/upload',
data: formData
})
task.then((data)=>{
//請求結束后將該Promise任務從并發池中移除
let index=pool.findIndex(t=> t===task)
pool.splice(index)
})
pool.push(task)
if(pool.length===max){
//每當并發池跑完一個任務,就再塞入一個任務
await Promise.race(pool)
}
}
//所有任務完成,合并切片
await axios({
method: 'get',
url: '/merge',
params: {
filename: file.name
}
});
console.log('上傳完成')
}
</script>
</html>
在單個請求失敗后,觸發catch的方法的時候,講當前請求放到失敗列表中,在本輪請求完成后,重復對失敗請求做處理,具體代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=s, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
<body>
<input type="file" id="fileInput">
<button id="uploadBtn">上傳</button>
</body>
<script>
// 請求基準地址
axios.defaults.baseURL='http://localhost:3000'
// 選中的文件
var file=null
// 選擇文件
document.getElementById('fileInput').onchange=function({target: {files}}){
file=files[0]
}
// 開始上傳
document.getElementById('uploadBtn').onclick=function(){
if (!file) return;
// 創建切片
// let size=1024 * 1024 * 10; //10MB 切片大小
let size=1024 * 50; //50KB 切片大小
let fileChunks=[];
let index=0 //切片序號
for(let cur=0; cur < file.size; cur +=size){
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size)
})
}
// 控制并發和斷點續傳
const uploadFileChunks=async function(list){
if(list.length===0){
//所有任務完成,合并切片
await axios({
method: 'get',
url: '/merge',
params: {
filename: file.name
}
});
console.log('上傳完成')
return
}
let pool=[]//并發池
let max=3 //最大并發量
let finish=0//完成的數量
let failList=[]//失敗的列表
for(let i=0;i<list.length;i++){
let item=list[i]
let formData=new FormData()
formData.append('filename', file.name)
formData.append('hash', item.hash)
formData.append('chunk', item.chunk)
// 上傳切片
let task=axios({
method: 'post',
url: '/upload',
data: formData
})
task.then((data)=>{
//請求結束后將該Promise任務從并發池中移除
let index=pool.findIndex(t=> t===task)
pool.splice(index)
}).catch(()=>{
failList.push(item)
}).finally(()=>{
finish++
//所有請求都請求完成
if(finish===list.length){
uploadFileChunks(failList)
}
})
pool.push(task)
if(pool.length===max){
//每當并發池跑完一個任務,就再塞入一個任務
await Promise.race(pool)
}
}
}
uploadFileChunks(fileChunks)
}
</script>
</html>
npm i express@4.17.2
npm i multiparty@4.2.2
const express=require('express')
const multiparty=require('multiparty')
const fs=require('fs')
const path=require('path')
const { Buffer }=require('buffer')
// 上傳文件最終路徑
const STATIC_FILES=path.join(__dirname, './static/files')
// 上傳文件臨時路徑
const STATIC_TEMPORARY=path.join(__dirname, './static/temporary')
const server=express()
// 靜態文件托管
server.use(express.static(path.join(__dirname, './dist')))
// 切片上傳的接口
server.post('/upload', (req, res)=> {
const form=new multiparty.Form();
form.parse(req, function(err, fields, files) {
let filename=fields.filename[0]
let hash=fields.hash[0]
let chunk=files.chunk[0]
let dir=`${STATIC_TEMPORARY}/${filename}`
// console.log(filename, hash, chunk)
try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir)
const buffer=fs.readFileSync(chunk.path)
const ws=fs.createWriteStream(`${dir}/${hash}`)
ws.write(buffer)
ws.close()
res.send(`${filename}-${hash} 切片上傳成功`)
} catch (error) {
console.error(error)
res.status(500).send(`${filename}-${hash} 切片上傳失敗`)
}
})
})
//合并切片接口
server.get('/merge', async (req, res)=> {
const { filename }=req.query
try {
let len=0
const bufferList=fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`).map((hash,index)=> {
const buffer=fs.readFileSync(`${STATIC_TEMPORARY}/${filename}/${index}`)
len +=buffer.length
return buffer
});
//合并文件
const buffer=Buffer.concat(bufferList, len);
const ws=fs.createWriteStream(`${STATIC_FILES}/${filename}`)
ws.write(buffer);
ws.close();
res.send(`切片合并完成`);
} catch (error) {
console.error(error);
}
})
server.listen(3000, _=> {
console.log('http://localhost:3000/')
})
如果使用騰訊云或阿里云文件上傳的服務,它們提供了npm庫,例如騰訊云的cos-js-sdk-v5,它自身提供的切片相關的配置
SS加載確實有可能阻塞頁面加載,但這并非絕對,具體取決于CSS的加載方式、應用位置以及瀏覽器的渲染機制。在了解CSS加載如何影響頁面加載之前,我們先要明白瀏覽器渲染頁面的基本流程。
瀏覽器在加載網頁時,會按照從上到下的順序解析HTML文檔。當瀏覽器遇到`<link>`標簽引用外部CSS文件時,它會停止HTML的解析,轉而加載并應用這個CSS文件。這個過程被稱為CSS阻塞。因此,如果這個CSS文件很大或者加載速度很慢,用戶可能會看到一個空白頁面,直到CSS文件完全加載并應用。
然而,有幾種方法可以避免或減輕CSS加載對頁面加載的阻塞:
此外,值得注意的是,現代瀏覽器通常具有一些優化機制,如并行下載、緩存等,這些都可以幫助減少CSS加載對頁面加載的影響。
總的來說,CSS加載確實有可能阻塞頁面加載,但通過一些優化策略和技術,我們可以減輕或避免這種阻塞。選擇哪種策略取決于你的具體需求和約束。
**前端實現大文件上傳**
**引言:**
隨著互聯網技術的發展,用戶在線處理大量數據的需求日益增強,其中涉及大文件上傳的功能已成為許多Web應用不可或缺的一部分。然而,傳統表單提交往往受限于瀏覽器的限制和服務器處理能力,無法很好地滿足大文件高效穩定上傳的需求。本文將深入探討前端實現大文件上傳的關鍵技術和策略,輔以實際HTML+JS代碼示例,助您構建高性能、用戶友好的文件上傳體驗。
## **一、理解瀏覽器上傳限制**
**1.1 瀏覽器最大請求大小限制**
大多數現代瀏覽器默認允許的最大HTTP POST請求大小約為2GB到4GB不等,但具體值會受到服務器配置的影響。因此,在實現大文件上傳之前,需要確保服務器端的接收限制足夠高。
**1.2 超時問題**
大文件上傳過程中,網絡狀況不佳或文件過大可能導致請求超時。對此,可通過設置合理的超時重試機制,以及使用分片上傳來解決。
## **二、分片上傳與斷點續傳**
**2.1 分片上傳概念**
分片上傳是將大文件分割成多個小塊,獨立上傳每一塊,最后在服務器端重組的方式。這樣可以有效避免一次性上傳大文件可能引發的問題。
```html
<!-- HTML 文件選擇器 -->
<input type="file" id="fileInput" accept=".zip,.rar">
<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const file=e.target.files[0];
// 假設每個分片大小為1MB
const chunkSize=1 * 1024 * 1024;
// 計算分片數量
const chunks=Math.ceil(file.size / chunkSize);
for (let i=0; i < chunks; i++) {
const start=i * chunkSize;
const end=Math.min(start + chunkSize, file.size);
// 創建File Slice
const chunk=file.slice(start, end);
// 發起異步上傳請求
uploadChunk(chunk, i, chunks);
}
});
function uploadChunk(chunk, index, total) {
// 這里僅展示發起上傳請求的邏輯,實際需要包含chunk索引和總數量等信息
const xhr=new XMLHttpRequest();
xhr.open('POST', '/api/upload/chunk', true);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.onload=()=> {
if (xhr.status===200) {
// 上傳成功處理邏輯
} else {
// 處理錯誤或重試
}
};
xhr.onerror=()=> {
// 錯誤處理
};
xhr.send(chunk);
}
</script>
```
**2.2 斷點續傳**
斷點續傳是在分片上傳的基礎上,記錄已上傳成功的分片信息,如果上傳過程因網絡問題中斷,可以從上次失敗的地方繼續上傳。這通常需要在客戶端存儲上傳進度信息,并在下次上傳時發送給服務器校驗。
```javascript
// 假設有本地持久化存儲已上傳分片信息的方法
function saveUploadProgress(progressData) {
localStorage.setItem('uploadProgress', JSON.stringify(progressData));
}
// 加載已上傳的分片信息
function loadUploadProgress() {
const progressData=localStorage.getItem('uploadProgress');
return progressData ? JSON.parse(progressData) : null;
}
// 在初始化上傳階段檢查并恢復未完成的上傳任務
const previousProgress=loadUploadProgress();
if (previousProgress) {
for (const {index, chunk} of previousProgress.unfinishedChunks) {
// 繼續上傳未完成的分片
uploadChunk(chunk, index, previousProgress.totalChunks);
}
}
```
## **三、前端上傳組件與庫推薦**
**3.1 React Dropzone Uploader**
React Dropzone Uploader是一個基于React的組件庫,內置了分片上傳和斷點續傳功能,可輕松集成至您的React項目中。
**3.2 Resumable.js**
Resumable.js 是一個輕量級、跨瀏覽器的大文件上傳庫,它支持分片上傳、斷點續傳及自定義事件通知等功能。
## **四、實時進度顯示與用戶體驗優化**
**4.1 實現上傳進度條**
在每個分片上傳完成后更新進度條,讓用戶體驗更加直觀。
```javascript
xhr.upload.onprogress=function(event) {
if (event.lengthComputable) {
const percentComplete=event.loaded / event.total;
updateProgressBar(percentComplete);
}
};
function updateProgressBar(percentage) {
// 更新頁面上的進度條UI
}
```
**4.2 錯誤處理與提示**
對于上傳過程中可能出現的各類錯誤,如網絡中斷、服務器異常等,都需要提供清晰且友好的錯誤提示,并賦予用戶重新上傳或恢復上傳的能力。
總結:
前端實現大文件上傳不僅涉及到技術層面的挑戰,還要求關注用戶體驗的設計。通過合理利用分片上傳、斷點續傳等技術,結合優秀的前端組件或庫,我們可以打造出穩定可靠、易用性高的大文件上傳功能,從而提升產品的綜合競爭力。同時,針對不同的業務場景,還需考慮文件安全性、并發控制、隊列管理等問題,確保整個上傳流程的健壯性。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。