大文件切片上传续传秒传,以及一些优化思考~
最近很多人在问,在 Vue3 中如何去做大文件的上传、暂停、续传,接下来就讲讲我的思路吧~

切片上传
大文件上传优化,肯定涉及到切片上传,顾名思义,就是把大文件切成一个一个的小片段,去上传,主要流程分为以下几步:
- 1.前端接收BGM并进行
切片
- 2.将每份
切片
都进行上传
- 3.后端接收到所有
切片
,创建一个文件夹
存储这些切片
- 4.后端将此
文件夹
里的所有切片合并为完整的BGM文件 - 5.删除
文件夹
,因为切片
不是我们最终想要的,可删除
- 6.当服务器已存在某一个文件时,再上传需要实现
“秒传”

后端代码准备
我这里用 Nodejs 模仿了后端,这不是本文章的重点,大家只要知道它实现了以下三个接口:
- upload: 切片文件的上传
- merge: 切片合并成大文件
- verify: 查询文件是否传输完了,如果没传输完,那么只上传了哪些部分

前端代码
以下是前端的代码
示例html
首先准备上传文件、进度条、上传、暂停、续传等 html 元素
<template>
<Input type="file" @change="hanleInputChange" />
<Progress :percent="percent" />
<Button @click="start" type="primary" class="mr-2">开始</Button>
<Button @click="pause">暂停</Button>
<Button @click="keep">续传</Button>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Button, Input, Progress } from 'ant-design-vue';
import axios, { type AxiosProgressEvent } from 'axios';
</script>
hanleInputChange存文件
这个函数是接收你上传的文件,并先存起来~
const file = ref<File | null>(null);
const hanleInputChange = (e: any) => {
// 把所需上传的文件先存起来
file.value = e.target.files[0];
};
开始上传
然后我们开始编写上传的代码,需要注意几件事情:
- 先定义好一个切片的尺寸是多少
- 把你的大文件分割成一个一个的小切片
- 所有切片进行上传
- 每个切片身上有一个finish,代表这个切片是否已上传完成
- 切片上传完后,发起合并请求
- 记得把CancelToken放在上传axios中,用于后续的暂停请求
interface IFileChunk {
file: Blob;
size: number;
finish: boolean;
chunkName: string;
fileName: string;
index: number;
}
// 存储切片
const chunkList = ref<IFileChunk[]>([]);
// 用于axios请求的取消
const CancelToken = axios.CancelToken;
let source = CancelToken.source();
// 每个切片的尺寸
const SIZE = 3 * 1024 * 1024;
// 创建切片
const createChunks = () => {
const fileName = file.value!.name;
const list: IFileChunk[] = [];
let s = 0;
let index = 0;
while (s < file.value!.size) {
const fileChunk = file.value!.slice(s, s + SIZE);
list.push({
file: fileChunk,
size: fileChunk.size,
finish: false,
chunkName: `${fileName}-${index}`,
fileName,
index,
});
s += SIZE;
index++;
}
chunkList.value = list;
};
// 监听上传过程的回调
const onUploadProgress = (index: number, e: AxiosProgressEvent) => {
const chunkItem = chunkList.value[index];
const { loaded, total } = e;
if (loaded >= total!) {
// 满足这个条件,代表这个切片已经上传完成
chunkItem.finish = true;
}
};
// 上传的请求函数
const upload = async (list?: IFileChunk[]) => {
const fileList = list ?? chunkList.value;
if (!fileList.length) return;
return Promise.all(
fileList
.map(({ file, fileName, index, chunkName }) => {
const formData = new FormData();
formData.append('file', file);
formData.append('fileName', fileName);
formData.append('chunkName', chunkName);
return { formData, index };
})
.map(({ formData, index }) =>
axios.post('http://localhost:3000/upload', formData, {
onUploadProgress: e => {
onUploadProgress(index, e);
},
cancelToken: source.token,
}),
),
);
};
// 合并的请求函数
const merge = () =>
axios.post(
'http://localhost:3000/merge',
JSON.stringify({
size: SIZE,
fileName: file.value!.name,
}),
{
headers: {
'content-type': 'application/json',
},
},
);
// 开始上传
const start = async () => {
if (!file.value) return;
createChunks();
await upload();
await merge();
};
百分比计算
刚刚说到了,每一个切片身上都有一个 finish 参数,记录这个切片是否已经上传完成了,我们想要计算百分比很简单,只需要知道有多少个 finish = true ,去除以总的切片数,就能得到百分比了
const percent = ref(0);
// 监听切片列表的变化
watch(
() => chunkList,
v => {
// 计算出多少个已经上传完成
const finishChunks = v.value.filter(({ finish }) => finish);
// 计算百分比
percent.value = Number((finishChunks.length / v.value.length).toFixed(2)) * 100;
},
{
deep: true,
},
);
暂停上传
还记得我们把 CancelToken 放在了上传请求中吗?我们想要暂停,只需要利用这个来取消上传的请求,就可以达到暂停的效果
要注意一个点:每次都要重置 source ,因为 source 已经被消费了,需要重置,下次才能继续取消请求~
const pause = () => {
source.cancel('中断上传!');
source = CancelToken.source();
};
续传
续传只需要请求 verify 接口,就能得知:
- 该不该续传?
- 如需续传,那是哪些切片需要续传?
const verify = async () => {
const { data } = await axios.post(
'http://localhost:3000/verify',
JSON.stringify({
fileName: file.value!.name,
}),
{
headers: {
'content-type': 'application/json',
},
},
);
return data;
};
const keep = async () => {
const { shouldUpload, uploadedList } = await verify();
// shouldUpload = true 说明不用续传了
if (!shouldUpload) return;
// 计算出哪些切片没有上传
const uploadList = chunkList.value.filter(({ chunkName }) => !uploadedList.includes(chunkName));
// 进行续传
upload(uploadList);
};
秒传
秒传就是:后端那边已经有这个文件了,你前端上传直接提示上传成功就行~
const start = async () => {
if (!file.value) return;
+ const { shouldUpload } = await verify();
+ if (!shouldUpload) {
+ console.log('上传成功');
+ return;
+ }
createChunks();
await upload();
await merge();
};
思考优化点
实现完上面功能之后,我们可以想象下有哪些地方可以优化,以下只是说一下想法,具体的代码实现,感兴趣的话可以以后开一个文章来讲
并发控制
切片太多了, 一股脑去上传,肯定会对浏览器和服务器造成很大负担,所以可以控制一下并发,使用p-limit
这个库,去控制一次只发一定数量的请求,进而达到并发控制的效果~
秒传优化
刚刚秒传是用文件名去判断的,这样肯定是不好的,最严谨的做法就是通过文件的hash值去判断,这是最准确的,这个hash值是需要在前端计算的,但是前端计算hash值可能有点慢,所以可以使用WebWorker
去优化