大文件分片上传-断点续传
简介
大文件分片上传是指将大型文件分割成小块进行上传,以提高上传效率和稳定性。这种方法通常用于网络上传或者文件传输中,特别是当上传的文件大小超过了服务器或网络的限制时。另外如果想做断点续传,文件分片也是必不可少的。
步骤
一般来说,大文件分片上传的实现可以通过以下步骤进行:
- 文件分割:将大文件按照固定大小或其他规则分割成多个片段,每个片段的大小要足够小以便于上传。
- 逐个上传:逐个上传每个文件片段到服务器,可以使用多线程或并行上传来提高效率。
- 上传完成标识:当所有文件片段都成功上传后,需要在服务器端对这些片段进行合并,并且标识整个文件上传完成。
- 断点续传:为了应对网络中断或其他意外情况,可以在上传过程中实现断点续传功能,即在上传中断后能够从中断处重新开始上传,而不需要重新上传整个文件。
案例
首先直接看效果
展示 👇以下案例可点击尝试模拟效果
文件分割
change
事件中获取文件流,再调用 slice
方法进行切片,拿到切片数组
js
// input change事件
function fileChange(event) {
const fileflow = event.target.files[0];
file.value = fileflow;
// 创建文件分片
const chunks = createChunks(fileflow);
refchunks.value = chunks;
upload();
}
// 创建文件分片
const createChunks = (file) => {
let start = 0;
const chunks = [];
while (start < file.size) {
chunks.push(file.slice(start, start + refchunkSize.value));
start += refchunkSize.value;
}
return chunks;
};
逐个上传
这边暂时还没有写后端 所以目前就写了一个模拟上传 等到后续写的时候再改回去
js
// 模拟上传
async function upload() {
const chunks = refchunks.value;
const maxParallelUploads = 5; // 最大并行上传数量
let currentIndex = 0; // 当前正在上传的分片索引
let uploadedCount = 0; // 已成功上传的分片数量
// 上传单个分片的函数
function uploadChunk(chunkIndex) {
// 开始上传 改变状态
refuploadSatus.value[chunkIndex] = "active";
// return
// 模拟上传时间,这里用 setTimeout 模拟异步上传
setTimeout(() => {
console.log("上传分片 " + chunkIndex + " 成功");
// 上传成功 改变状态
refuploadSatus.value[chunkIndex] = "success";
uploadedCount++;
// 如果还有未上传的分片,并且当前并行上传数量小于最大并行上传数量
if (
currentIndex < chunks.length &&
currentIndex - uploadedCount < maxParallelUploads
) {
uploadChunk(currentIndex); // 开始上传下一个分片
currentIndex++;
}
// 如果所有分片都上传完成
if (uploadedCount === chunks.length) {
console.log("所有分片上传完成");
}
}, Math.random() * 2000); // 为了模拟上传时间随机设置延迟
}
// 启动初始的最大并行上传
for (let i = 0; i < maxParallelUploads && i < chunks.length; i++) {
uploadChunk(currentIndex);
currentIndex++;
}
}
完整代码
vue
<template>
<div class="upload">
<input ref="fileUpload" type="file" value="" @change="fileChange" />
<div v-if="file" class="fileInfo">
<div class="chunksBox">
<div
v-for="(item, index) in refchunks"
class="chunk ready"
:class="uploadStatus(index)"
>
<p>分片{{ index }}</p>
<p>大小:{{ kb2mb(item.size) }}Mb</p>
</div>
</div>
<p>文件大小: {{ kb2mb(file.size) }}Mb</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from "vue";
const fileUpload = ref(null);
const file = ref(null);
const refchunks = ref(null);
const refchunkSize = ref(1024 * 1024); // 1MB
const refuploadSatus = ref({});
// 创建一个计算属性,转换单位
const kb2mb = computed(() => {
return function (size) {
return (size / 1024 / 1024).toFixed(2);
};
});
// 创建一个计算属性,将number加倍
const uploadStatus = computed(() => {
return function (index) {
return refuploadSatus.value[index];
};
});
// inputchange事件
function fileChange(event) {
const fileflow = event.target.files[0];
file.value = fileflow;
// 创建文件分片
const chunks = createChunks(fileflow);
refchunks.value = chunks;
upload();
}
// 创建文件分片
const createChunks = (file) => {
let start = 0;
const chunks = [];
while (start < file.size) {
chunks.push(file.slice(start, start + refchunkSize.value));
start += refchunkSize.value;
}
return chunks;
};
// 模拟上传
async function upload() {
const chunks = refchunks.value;
const maxParallelUploads = 5; // 最大并行上传数量
let currentIndex = 0; // 当前正在上传的分片索引
let uploadedCount = 0; // 已成功上传的分片数量
// 上传单个分片的函数
function uploadChunk(chunkIndex) {
// 开始上传 改变状态
refuploadSatus.value[chunkIndex] = "active";
// return
// 模拟上传时间,这里用 setTimeout 模拟异步上传
setTimeout(() => {
console.log("上传分片 " + chunkIndex + " 成功");
// 上传成功 改变状态
refuploadSatus.value[chunkIndex] = "success";
uploadedCount++;
// 如果还有未上传的分片,并且当前并行上传数量小于最大并行上传数量
if (
currentIndex < chunks.length &&
currentIndex - uploadedCount < maxParallelUploads
) {
uploadChunk(currentIndex); // 开始上传下一个分片
currentIndex++;
}
// 如果所有分片都上传完成
if (uploadedCount === chunks.length) {
console.log("所有分片上传完成");
}
}, Math.random() * 2000); // 为了模拟上传时间随机设置延迟
}
// 启动初始的最大并行上传
for (let i = 0; i < maxParallelUploads && i < chunks.length; i++) {
uploadChunk(currentIndex);
currentIndex++;
}
}
</script>
<style lang="scss" scoped>
.fileInfo {
.chunksBox {
display: flex;
flex-wrap: wrap;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
.chunk {
width: 20%;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
text-align: center;
position: relative;
overflow: hidden;
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
z-index: -1;
background: linear-gradient(to right, #8bc34a, #4caf50);
transform: translateX(-100%); /* 修改这里 */
transition: transform 10s ease;
}
}
.ready::after {
}
.active::after {
transform: translateX(-1%); /* 修改这里 */
}
.success {
&::after {
transition: transform 0.3s ease;
transform: translateX(0);
}
}
}
}
</style>