feat: chunk uploads
This commit is contained in:
140
frontend/src/components/ChunkUpload.vue
Normal file
140
frontend/src/components/ChunkUpload.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<FileUpload :customUpload="true" @uploader="handleUpload" multiple :maxFileSize="50000000">
|
||||
<template #empty>
|
||||
<p>拖拽文件到此处上传</p>
|
||||
</template>
|
||||
</FileUpload>
|
||||
<ProgressBar v-if="progress > 0" :value="progress" />
|
||||
<small v-if="progress > 0">{{ status }}</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB chunks
|
||||
const progress = ref(0);
|
||||
const status = ref('');
|
||||
|
||||
const createChunks = (file) => {
|
||||
const chunks = [];
|
||||
let start = 0;
|
||||
while (start < file.size) {
|
||||
chunks.push(file.slice(start, start + CHUNK_SIZE));
|
||||
start += CHUNK_SIZE;
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
|
||||
const calculateFileMD5 = async (file) => {
|
||||
status.value = '计算文件MD5...';
|
||||
progress.value = 0;
|
||||
|
||||
const chunkSize = 2097152; // 2MB
|
||||
const chunks = Math.ceil(file.size / chunkSize);
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
|
||||
const fileReader = new FileReader();
|
||||
let currentChunk = 0;
|
||||
|
||||
const readNextChunk = () => {
|
||||
const start = currentChunk * chunkSize;
|
||||
const end = Math.min(file.size, start + chunkSize);
|
||||
fileReader.readAsArrayBuffer(file.slice(start, end));
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fileReader.onload = (e) => {
|
||||
spark.append(e.target.result);
|
||||
currentChunk++;
|
||||
progress.value = Math.round((currentChunk / chunks) * 100);
|
||||
|
||||
if (currentChunk < chunks) {
|
||||
readNextChunk();
|
||||
} else {
|
||||
const md5 = spark.end();
|
||||
progress.value = 0;
|
||||
resolve(md5);
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.onerror = () => {
|
||||
reject('MD5计算失败');
|
||||
};
|
||||
|
||||
readNextChunk();
|
||||
});
|
||||
};
|
||||
|
||||
const MAX_CONCURRENT_UPLOADS = 3;
|
||||
|
||||
const uploadChunks = async (chunks, file, fileMD5) => {
|
||||
const pending = [...Array(chunks.length).keys()];
|
||||
const uploading = new Set();
|
||||
const completed = new Set();
|
||||
|
||||
while (pending.length > 0 || uploading.size > 0) {
|
||||
while (uploading.size < MAX_CONCURRENT_UPLOADS && pending.length > 0) {
|
||||
const index = pending.shift();
|
||||
uploading.add(index);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', chunks[index]);
|
||||
formData.append('fileName', file.name);
|
||||
formData.append('chunkNumber', index);
|
||||
formData.append('totalChunks', chunks.length);
|
||||
formData.append('fileMD5', fileMD5);
|
||||
|
||||
uploadChunk(formData, index)
|
||||
.then(() => {
|
||||
uploading.delete(index);
|
||||
completed.add(index);
|
||||
progress.value = Math.round((completed.size / chunks.length) * 100);
|
||||
})
|
||||
.catch((error) => {
|
||||
uploading.delete(index);
|
||||
pending.push(index);
|
||||
console.error(`Chunk ${index} failed:`, error);
|
||||
});
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChunk = async (formData, index) => {
|
||||
const response = await fetch('/api/v1/medias/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const handleUpload = async (event) => {
|
||||
const files = event.files;
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const fileMD5 = await calculateFileMD5(file);
|
||||
status.value = '开始上传文件...';
|
||||
const chunks = createChunks(file);
|
||||
|
||||
await uploadChunks(chunks, file, fileMD5);
|
||||
|
||||
status.value = '上传完成';
|
||||
setTimeout(() => {
|
||||
progress.value = 0;
|
||||
status.value = '';
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
status.value = '上传失败: ' + error;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user