Node.js實(shí)現(xiàn)分片上傳斷點(diǎn)續(xù)傳示例詳解
正文
大文件上傳會(huì)消耗大量的時(shí)間,而且中途有可能上傳失敗。這時(shí)我們需要前端和后端配合來(lái)解決這個(gè)問(wèn)題。
解決步驟:
- 文件分片,減少每次請(qǐng)求消耗的時(shí)間,如果某次請(qǐng)求失敗可以單獨(dú)上傳,而不是從頭開(kāi)始
- 通知服務(wù)端合并文件分片
- 控制并發(fā)的請(qǐng)求數(shù)量,避免瀏覽器內(nèi)存溢出
- 當(dāng)因?yàn)榫W(wǎng)絡(luò)或者其他原因?qū)е履炒蔚恼?qǐng)求失敗,我們重新發(fā)送請(qǐng)求
文件的分片與合并
在JavaScript中,F(xiàn)Ile對(duì)象是' Blob '對(duì)象的子類,該對(duì)象包含一個(gè)重要的方法slice,通過(guò)該方法我們可以這樣分割二進(jìn)制文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, 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" multiple="multiple" id="fileInput" />
<button onclick="SliceUpload()">上傳</button>
<script>
function SliceUpload() {
const file = document.getElementById('fileInput').files[0]
if (!file) return
// 文件分片
let size = 1024 * 50; //50KB 50KB Section size
let fileChunks = [];
let index = 0; //Section num
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);
// 所有分片上傳完成,通知服務(wù)器合并分片
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
console.log("Upload to complete");
}
</script>
</body>
</html>并發(fā)控制
如果文件很大,這樣切分的分片會(huì)很多,瀏覽器短時(shí)間內(nèi)就會(huì)發(fā)起大量的請(qǐng)求,可能會(huì)導(dǎo)致內(nèi)存耗盡,所以要進(jìn)行并發(fā)控制。
這里我們結(jié)合Promise.race()方法 控制并發(fā)請(qǐng)求的數(shù)量,避免瀏覽器內(nèi)存溢出。
// 加入并發(fā)控制
async function SliceUpload() {
const file = document.getElementById('fileInput').files[0]
if (!file) return
// 文件分片
let size = 1024 * 50; //50KB 50KB Section size
let fileChunks = [];
let index = 0; //Section num
for (let cur = 0; cur < file.size; cur += size) {
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size),
});
}
let pool = []; //Concurrent pool
let max = 3; //Maximum concurrency
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(() => {
// 從并發(fā)池中移除已經(jīng)完成的請(qǐng)求
let index = pool.findIndex((t) => t === task);
pool.splice(index);
});
// 把請(qǐng)求放入并發(fā)池中,如果已經(jīng)達(dá)到最大并發(fā)量
pool.push(task);
if (pool.length === max) {
//All requests are requested complete
await Promise.race(pool);
}
}
// 所有分片上傳完成,通知服務(wù)器合并分片
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
console.log("Upload to complete");
}使代碼可復(fù)用
function SliceUpload() {
const file = document.getElementById('fileInput').files[0]
if (!file) return
// 文件分片
let size = 1024 * 50; // 分片大小設(shè)置
let fileChunks = [];
let index = 0; // 分片序號(hào)
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){
// 所有分片上傳完成,通知如無(wú)
await axios({
method: 'get',
url: '/merge',
params: {
filename: file.name
}
});
console.log('Upload to complete')
return
}
let pool = [] // 并發(fā)池
let max = 3 // 最大并發(fā)數(shù)
let finish = 0 // 完成數(shù)量
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)=>{
// 從并發(fā)池中移除已經(jīng)完成的請(qǐng)求
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)
}服務(wù)端接口實(shí)現(xiàn)
const express = require('express')
const multiparty = require('multiparty')
const fs = require('fs')
const path = require('path')
const { Buffer } = require('buffer')
// file path
const STATIC_FILES = path.join(__dirname, './static/files')
// Temporary path to upload files
const STATIC_TEMPORARY = path.join(__dirname, './static/temporary')
const server = express()
// Static file hosting
server.use(express.static(path.join(__dirname, './dist')))
// Interface for uploading slices
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)以上就是Node.js實(shí)現(xiàn)分片上傳示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Node.js分片上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
手把手教你更優(yōu)雅的修改node_modules里的代碼
這篇文章主要給大家介紹了關(guān)于如何更優(yōu)雅的修改node_modules里的代碼的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-02-02
Node.js API詳解之 timer模塊用法實(shí)例分析
這篇文章主要介紹了Node.js API詳解之 timer模塊用法,結(jié)合實(shí)例形式分析了Node.js API中timer模塊基本功能、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-05-05
Nodejs爬蟲(chóng)進(jìn)階教程之異步并發(fā)控制
這篇文章主要介紹了Nodejs爬蟲(chóng)進(jìn)階教程之異步并發(fā)控制的相關(guān)資料,需要的朋友可以參考下2016-02-02
nodejs安裝與配置過(guò)程+初學(xué)實(shí)例解讀
這篇文章主要介紹了nodejs安裝與配置過(guò)程+初學(xué)實(shí)例解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04

