问题:耗时久,网络断开导致上传失败
方案:利用分片上传,结合md5计算hash ,结合wenworker创建线程解决主线程阻塞
步骤:
1.分片:获取文件--获取尺寸--按照分片大小切割--利用md5计算出单个chunk的hash和整文件hash---创建formData实例 ,传给后端--利用promise.all全部传递成功调用后端完成接口实现
handleInputFile = async() => {
if (this.fileInput.current.files[0]) {
console.log(this.fileInput.current.files[0])
const file = this.fileInput.current.files[0]
let fileHash
//利用webworker实现hash计算
const worker = new Worker('/hash.js')
worker.postMessage(file)
worker.onmessage = (e) => {
console.log(e.data)
fileHash = e.data
}
const chunkSize = 1024 * 2
const total = Math.ceil(file.size/chunkSize)
const chunkArray = []
for( let i = 0; i < total; i++){
let start = i * chunkSize
let end=Math.min(start + chunkSize, file.size)
const fileInfo = file.slice(start,end)
chunkArray.push(this.uploadChunk(fileInfo,i))
}
console.log(chunkArray)
await promises.all(chunkArray)
//调用全部上传成功标识接口
try {
await axios.post('http://localhost:3000/merge', {
fileHash,
total
});
console.log('文件合并完成');
} catch (error) {
console.error('文件合并出错:', error);
}
} else {
this.setState({
fileValue: null
})
}
}
uploadChunk = (fileInfo,i) => {
let chunkhash
//利用webworker实现hash计算
const worker = new Worker('/hash.js')
worker.postMessage(fileInfo,i)
worker.onmessage = (e) => {
console.log(e.data)
chunkhash = e.data
}
const formData = new FormData()
formData.append('file', fileInfo)
formData.append('chunkNumber', i)
formData.append('chunkSize', 1024 * 2)
formData.append('currentChunkSize', fileInfo.size)
formData.append('chunkhash', chunkhash)
try{
//传给接口
return new Promise((resolve,reject) => {
const api = axios.create({
baseURL: 'http://localhost:8080/',
timeout: 10000,
headers: {
'Content-Type': 'multipart/form-data'
}
})
api.post('/upload',formData).then((res) => {
resolve(res)
}).catch((err) => {})
})
}catch(error){
console.log(error)
}
}
// 引入 md5 库
importScripts('https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js');
// 监听 message 事件,接收主线程发送的数据
self.onmessage = function (event) {
const file = event.data;
const reader = new FileReader();
// 读取文件内容
reader.readAsArrayBuffer(file);
// 读取完成后计算 MD5 哈希值
reader.onload = function () {
const arrayBuffer = reader.result;
const md5Hash = md5(arrayBuffer);
// 将计算结果返回给主线程
self.postMessage(md5Hash);
};
reader.onerror = function () {
console.error('文件读取出错');
};
};
可优化点:断点续传 放置在网络或者其他条件导致的上传失败
在每次上传前调用接口查询 哪些分片还没有上传成功
<script>
const fileInput = document.getElementById('fileInput');
const uploadButton = document.getElementById('uploadButton');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
uploadButton.addEventListener('click', async () => {
const file = fileInput.files[0];
if (!file) return;
const chunkSize = 1024 * 1024; // 每个文件块的大小为 1MB
const totalChunks = Math.ceil(file.size / chunkSize);
const fileHash = md5(file);
let uploadedChunks = 0;
// 检查已上传的文件块
const response = await axios.get('http://localhost:3000/check', {
params: {
fileHash
}
});
const uploadedIndices = response.data;
const uploadChunk = async (chunk, index) => {
const chunkHash = md5(chunk);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', index);
formData.append('totalChunks', totalChunks);
formData.append('fileHash', fileHash);
formData.append('chunkHash', chunkHash);
try {
await axios.post('http://localhost:3000/upload', formData);
uploadedChunks++;
const progress = (uploadedChunks / totalChunks) * 100;
progressBar.style.width = `${progress}%`;
progressText.textContent = `上传进度: ${progress.toFixed(2)}%`;
} catch (error) {
console.error('上传出错:', error);
}
};
const promises = [];
for (let i = 0; i < totalChunks; i++) {
if (!uploadedIndices.includes(i)) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
promises.push(uploadChunk(chunk, i));
}
}
await Promise.all(promises);
// 所有文件块上传完成,发送合并请求
try {
await axios.post('http://localhost:3000/merge', {
fileHash,
totalChunks
});
console.log('文件合并完成');
} catch (error) {
console.error('文件合并出错:', error);
}
});
</script>