一、概述
本示例实现了一个基于 Vue3 和 TypeScript 的断点上传功能。该功能支持文件分块上传,能够在上传过程中暂停、继续上传,并且支持检测已经上传的分块,避免重复上传,提升上传效率。以下是关键的技术点与实现流程:
- 文件分块:将大文件分成多个小块,每块的大小是固定的(例如 5MB)。
- 上传进度:通过进度条显示文件上传的进度。
- 断点续传:支持暂停和继续上传,避免上传过程中断导致的文件重新上传。
- 文件哈希:通过计算文件的哈希值来唯一标识文件,并检查文件是否已经上传完成。
二、实现步骤
(一)定义状态变量
通过 ref
定义一些响应式状态变量来管理文件、进度、上传状态等信息:
const file = ref<File | null>(null);
const fileHash = ref<string>('');
const chunkSize = 5 * 1024 * 1024; // 每个分块大小(5MB)
const uploadedChunks = ref<number[]>([]); // 已上传的分块索引
const progress = ref<number>(0); // 上传进度
const paused = ref<boolean>(false); // 上传是否暂停
const uploading = ref<boolean>(false); // 是否正在上传
(二)监听文件选择并计算哈希
当用户选择文件后,计算该文件的 SHA-256 哈希值,哈希值用来判断文件是否已经上传过。
const onFileChange = (event: Event) => {
const target = event.target as HTMLInputElement;
file.value = target.files?.[0] || null;
if (file.value) {
calculateFileHash();
}
};
const calculateFileHash = async () => {
if (!file.value) return;
const buffer = await file.value.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
fileHash.value = [...new Uint8Array(hashBuffer)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
await checkUploadedChunks();
};
(三)检查已上传的分块
调用后端接口检查文件的部分分块是否已经上传,以避免重复上传。
const checkUploadedChunks = async () => {
try {
const response = await axios.get(getUploadedChunksApi.value, {
params: { fileHash: fileHash.value },
});
if (response.data.uploadedChunks === 'completed') {
alert('文件已上传完成!');
progress.value = 100; // 设置进度为 100%
return true; // 文件已完成上传
}
uploadedChunks.value = response.data.uploadedChunks || [];
return false; // 文件未完成上传
} catch (error) {
console.error('Error checking uploaded chunks:', error);
return false;
}
};
(四)开始上传
分块上传文件。上传时检查哪些分块已经上传,跳过已上传的分块。如果上传中出现错误,可以暂停上传。
const startUpload = async () => {
if (!file.value) return;
paused.value = false;
uploading.value = true;
const isCompleted = await checkUploadedChunks();
if (isCompleted) {
uploading.value = false;
return;
}
const totalChunks = Math.ceil(file.value.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
if (paused.value) break;
if (uploadedChunks.value.includes(i)) {
progress.value = ((i + 1) / totalChunks) * 100;
continue;
}
const chunk = file.value.slice(i * chunkSize, (i + 1) * chunkSize);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', i.toString());
formData.append('fileHash', fileHash.value);
formData.append('totalChunks', totalChunks.toString());
try {
await axios.post(uploadChunkApi.value, formData);
uploadedChunks.value.push(i);
progress.value = ((i + 1) / totalChunks) * 100;
} catch (error) {
console.error('Error uploading chunk:', error);
paused.value = true;
break;
}
}
if (!paused.value && progress.value === 100) {
completeUpload();
}
uploading.value = false;
};
(五)暂停与继续上传
提供暂停与继续上传的功能,通过控制 paused
变量来实现。
const pauseUpload = () => {
paused.value = true;
uploading.value = false;
};
const resumeUpload = () => {
paused.value = false;
startUpload();
};
(六)合并分块
所有分块上传完成后,通过后端接口提交合并请求,合并文件。
const completeUpload = async () => {
try {
await axios.post(completeUploadApi.value, {
fileHash: fileHash.value,
fileExtension: file.value?.name.split('.').pop(),
fileName: file.value?.name,
});
alert('文件上传完成!');
} catch (error) {
console.error('Error completing upload:', error);
}
};
三、模板部分
<template>
<div class="upload">
<h2>断点上传</h2>
<input type="file" @change="onFileChange" />
<div v-if="file">
<p>文件名: {{ file.name }}</p>
<p>文件大小: {{ (file.size / 1024 / 1024).toFixed(2) }} MB</p>
<progress :value="progress" max="100"></progress>
<div class="actions">
<button @click="startUpload" :disabled="uploading">开始上传</button>
<button @click="pauseUpload" :disabled="!uploading">暂停上传</button>
<button @click="resumeUpload" :disabled="uploading || !paused">
继续上传
</button>
</div>
</div>
</div>
</template>
四、样式部分
<style scoped>
.upload {
width: 400px;
margin: 20px auto;
}
.actions {
margin-top: 10px;
}
button {
margin-right: 10px;
}
progress {
width: 100%;
height: 20px;
margin-top: 10px;
}
</style>
五、总结
通过以上的实现,我们可以完成一个支持断点续传和分块上传的功能。该方案不仅提升了大文件上传的效率,还避免了上传过程中断的影响。通过 Vue3 与 TypeScript 的结合,实现了清晰的状态管理和组件化的文件上传逻辑,便于后续的维护和扩展。