大文件上传的那些坑:分片上传+断点续传
业务背景
一个智能营销SaaS系统,用户需要上传视频文件,最大的可能有几百MB。一开始直接用Element UI的Upload组件,传个几十MB还行,但一超过100MB就开始各种问题:网络稍微抖一下就断了,断了还得从头传,用户体验很糟糕。
后来决定上阿里云OSS,用他们的分片上传。
技术方案
阿里云OSS提供了multipartUpload方法,官方文档说是支持断点续传。我看了一下文档,感觉挺简单的,就直接用了。
核心代码其实不多:
let OSS = require('ali-oss')
let client = new OSS({
region: '', //创建的时候,bucket所在的区域,华北2->oss-cn-beijing ;其他的可以去百度
accessKeyId: 'LTXXXXXXXX', // 阿里云控制台创建的AccessKey
accessKeySecret: 'XXXXXXXXXXXX', //阿里云控制台创建的AccessSecret
bucket: 'xingge-ai' //创建的bucket的名称
})
export const put = async (ObjName, fileUrl, updloadFileInfo) => {
try {
const result = await client.multipartUpload(`${ObjName}`, fileUrl, {
timeout: 600000, // 10分钟超时
progress: (p) => {
updloadFileInfo.progress = Math.floor(p * 100); // 进度回调
}
})
const fileInfo = {
name: result.name,
url: `https://${client.options.bucket}.${client.options.region}.aliyuncs.com/${result.name}`,
res: {
status: result.res.status,
}
}
return fileInfo
} catch (e) {
console.log(e)
}
}
就这几十行代码,看着挺清爽的。multipartUpload会自动把文件切成片上传,然后服务端合并。progress回调可以直接拿到上传进度,在组件里显示进度条就行。
第一个坑:超时时间
一开始没设置timeout,传个大文件传到一半就报错了。查了一下文档才发现默认超时时间很短,对于几百MB的文件根本不够。
timeout: 600000, // 改成10分钟,单位是毫秒
这个要根据自己的业务场景调整。我们这边用户网络情况不太好,就设置得长一些。
第二个坑:进度卡住不动
上传的时候发现进度条有时候会卡在某个百分比不动,过一会儿才继续跳。查了半天发现是阿里云SDK内部做了优化,不是每个分片上传完都会触发progress回调。
这个没办法改,只能在UI上加个loading提示,告诉用户正在上传,别以为卡死了:
第三个坑:断点续传怎么实现
官方文档说multipartUpload支持断点续传,但代码层面怎么实现没说清楚。
我们的做法是:
- 上传前先记录文件信息(文件名、大小、修改时间)
- 存到localStorage或者IndexedDB里
- 如果用户刷新页面或者网络断了,重新上传时先检查这个文件有没有传过一半
- 如果有,就调用
multipartUpload的checkpoint参数继续传
具体代码有点长,这里就不贴了,核心思想就是记录uploadId,续传的时候传进去。
阿里云会根据uploadId找到已经上传的分片,从断掉的地方继续传。
第四个坑:文件名重复
一开始直接用文件原名上传,结果用户传两个同名文件就冲突了。后来加了个UUID后缀:
export const getFileNameUUID = () => {
function rx() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
}
return `${+new Date()}_${rx()}${rx()}`
}
这样上传的时候:原始文件名.mp4 -> 20240301_abc123def456_原始文件名.mp4
既保留原名又保证唯一性。
一些小技巧
1. 文件类型校验 在上传前就校验文件类型,别传到一半才发现格式不对:
handleBeforeUpload(file) {
const fileName = file.name.split(".");
const fileExt = fileName[fileName.length - 1].toLowerCase();
const isTypeOk = this.fileType.indexOf(fileExt) >= 0;
if (!isTypeOk) {
this.$message.error(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
return false;
}
// ...
}
2. 文件大小限制 前端先拦截一下,别让用户传了半天才发现文件太大:
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
return false;
}
3. 拖拽上传 用拖拽的方式上传体验会好很多,尤其是大文件:
>
拖动文件至此,或者点击选择文件
总结
做完这个功能:
- 别相信默认值:timeout、分片大小这些参数,一定要根据实际业务场景调整
- 用户体验很重要:进度条、loading提示、错误提示,这些细节要考虑清楚
- 测试要充分:弱网、断网、大文件、小文件,各种情况都要测










