大文件上传-分片-秒传-断点续传
项目源码资源 Gitee (opens new window)
# 什么是分片上传、秒传、断点续传
# 分片上传
小文件(图片、文档、视频)上传可以直接使用很多ui框架封装的上传组件,或者自己写一个input 上传,利用FormData
对象提交文件数据,后端使用 Spring 提供的MultipartFile
进行文件的接收,然后写入即可。但是对于比较大的文件,比如上传2G左右的文件(http上传),就需要将文件分片上传(借助于file.slice()
),否则中间http长时间连接可能会断掉。
分片上传就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
# 断点续传
断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景。
# 秒传
秒传通俗地说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了。
# 整体的流程设计
这里我们将整个流程的参与者分为,前端(页面)、后端、数据库、存储服务
# 技术方案说明
为了方便演示使用,本项目使用的是前后端不分离的架构
前端
- 文件操作使用 simple-uploader GitHub (opens new window) 、参数说明 (opens new window)
- 使用前一定要去一下 simple-uploader 相关参数,钩子函数等,不然会迷茫
- 签名使用 spark-md5.js
- 页面用的 element-ui
后端
- SpringBoot 2.7
- MySQL 5.7
- MyBats-plus 3.5.2
- 存储使用本地存储
# 核心代码说明
其实整个过程虽然复杂,但是其核心无非是两点
# 前端文件拆分
主要依靠文件 api let fileReader = new FileReader();
以及 File.prototype.slice
来实现功能
//获取文件分片对象(注意它的兼容性,在不同浏览器的写法不同)
let blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice;
// 调用切片
blobSlice.call(file.file, start, end)
// 加载
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
2
3
4
5
6
7
8
9
详细代码请看Gitee (opens new window)
# 后端文件分片
这里我是用的是本地文件的存储,可以使用uploadFileByRandomAccessFile
或者 uploadFileByMappedByteBuffer
来实现
如果用的是第三方存储,那么只要调用第三方提供的分片上传 api 即可
// try 自动资源管理
try (RandomAccessFile randomAccessFile = new RandomAccessFile(fullFileName, "rw")) {
// 分片大小必须和前端匹配,否则上传会导致文件损坏
long chunkSize = dto.getChunkSize() == 0L ? defaultChunkSize : dto.getChunkSize().longValue();
// 偏移量, 意思是我从拿一个位置开始往文件写入,每一片的大小 * 已经存的块数
long offset = chunkSize * (dto.getChunkNumber() - 1);
// 定位到该分片的偏移量
randomAccessFile.seek(offset);
// 写入
randomAccessFile.write(dto.getFile().getBytes());
} catch (IOException e) {
log.error("文件上传失败:" + e);
return Boolean.FALSE;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
详细代码请看Gitee (opens new window)