基于SpringBoot和WebUploader实现大文件分块上传.断点续传.秒传
大文件面临的问题
- 上传速度慢 -- 应对: 分块上传
- 上传文件到一半中断后,继续上传却只能重头开始上传 -- 应对: 断点续传
- 相同文件未修改再次上传, 却只能重头开始上传 -- 应对: 秒传
分片上传
1、什么分片上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
2、分片上传适用场景
- 大文件上传
- 网络环境环境不好,存在需要重传风险的场景
3、上传的具体流程
因为这个上传流程和断点续传类似,就在下边介绍断点续传中介绍
断点续传
1、什么是断点续传
断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景
2、应用场景
断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传
3、实现断点续传的核心逻辑
在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。
为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。
4、实现流程步骤
- 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小
- 服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤)
- 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件
秒传
1、什么是秒传
通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了
2、本文实现的秒传核心逻辑
a、利用redis的set方法存放文件上传状态,其中key为文件上传的md5,value为是否上传完成的标志位,
b、当标志位true为上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为false,则说明还没上传完成,此时需要在调用set的方法,保存块号文件记录的路径,其中key为上传文件md5加一个固定前缀,value为块号文件记录路径
WebUploader
1,什么是 WebUploader?
WebUploader 是由百度公司团队开发的一个以 HTML5 为主,FLASH 为辅的现代文件上传组件。
2,功能特点
- 分片、并发:WebUploader 采用大文件分片并发上传,极大的提高了文件上传效率。
- 预览、压缩:WebUploader 支持常用图片格式 jpg,jpeg,gif,bmp,png 预览与压缩,节省网络数据传输。
- 多途径添加文件:支持文件多选,类型过滤,拖拽(文件 & 文件夹),图片粘贴功能。
- HTML5 & FLASH:兼容主流浏览器,接口一致,实现了两套运行时支持,用户无需关心内部用了什么内核。
- MD5 秒传:当文件体积大、量比较多时,支持上传前做文件 md5 值验证,一致则可直接跳过。
- 易扩展、可拆分:采用可拆分机制, 将各个功能独立成了小组件,可自由搭配。
3. 接口说明
before-send-file
此hook在文件发送之前执行before-file
此hook在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行。after-send-file
此hook在文件所有分片都上传完后,且服务端没有错误返回后执行。
Web Uploader的所有代码都在一个内部闭包中,对外暴露了唯一的一个变量WebUploader
,所以完全不用担心此框架会与其他框架冲突。
内部所有的类和功能都暴露在WebUploader
名字空间下面。
Demo中使用的是WebUploader.create
方法来初始化的,实际上可直接访问WebUploader.Uploader
。
var uploader = new WebUploader.Uploader({
swf: 'path_of_swf/Uploader.swf'
// 其他配置项
});
具体有哪些内部类,请转到API页面。
4. 事件
Uploader
实例具有Backbone同样的事件API:on
,off
,once
,trigger
。
uploader.on( 'fileQueued', function( file ) {
// do some things.
});
除了通过on
绑定事件外,Uploader
实例还有一个更便捷的添加事件方式。
uploader.onFileQueued = function( file ) {
// do some things.
};
如同Document Element
中的onEvent一样,他的执行比on
添加的handler
的要晚。如果那些handler
里面,有一个return false
了,此onEvent
里面是不会执行到的。
5. Hook
Uploader
里面的功能被拆分成了好几个widget
,由command
机制来通信合作。
如下,filepicker在用户选择文件后,直接把结果request
出去,然后负责队列的queue
widget,监听命令,根据配置项中的accept
来决定是否加入队列。
// in file picker
picker.on( 'select', function( files ) {
me.owner.request( 'add-file', [ files ]);
});
// in queue picker
Uploader.register({
'add-file': 'addFiles'
// xxxx
}, {
addFiles: function( files ) {
// 遍历files中的文件, 过滤掉不满足规则的。
}
});
Uploader.regeister
方法用来说明,该widget
要响应哪些命令,并指定由什么方法来响应。上面的例子,当add-file
命令派送时,内部的addFiles
成员方法将被执行到,同一个命令,可以指定多次handler
, 各个handler
会按添加顺序依次执行,且后续的handler
,不能被前面的handler
截断。
handler
里面可以是同步过程,也可以是异步过程。是异步过程时,只需要返回一个promise
对象即可。存在异步可能的request调用者会等待此过程结束后才继续。举个例子,webuploader运行在flash模式下时,需要等待flash加载完毕后才能算ready了,此过程为一个异步过程,目前的做法是如下:
// uploader在初始化的时候
me.request( 'init', opts, function() {
me.state = 'ready';
me.trigger('ready');
});
// filepicker `widget`中的初始化过程。
Uploader.register({
'init': 'init'
}, {
init: function( opts ) {
var deferred = Base.Deferred();
// 加载flash
// 当flash ready执行deferred.resolve方法。
return deferred.promise();
}
});
目前webuploader内部有很多种command,在此列出比较重要的几个。
名称 | 参数 | 说明 |
add-file |
files: File对象或者File数组 | 用来向队列中添加文件。 |
before-send-file |
file: File对象 | 在文件发送之前request,此时还没有分片(如果配置了分片的话),可以用来做文件整体md5验证。 |
before-send |
block: 分片对象 | 在分片发送之前request,可以用来做分片验证,如果此分片已经上传成功了,可返回一个rejected promise来跳过此分片上传 |
after-send-file |
file: File对象 | 在所有分片都上传完毕后,且没有错误后request,用来做分片验证,此时如果promise被reject,当前文件上传会触发错误。 |
代码实现:基于SpringBoot和WebUploader
页面效果
前端
<html>
<head>
<meta charset="utf-8">
<title>BigFile-WebUploader</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="css/webuploader.css">
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/webuploader.js"></script>
</head>
<body>
<div id="uploader" class="wu-example">
<div id="thelist" class="uploader-list"></div>
<div class="btns">
<div id="picker">选择大文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>
<button id="stopBtn" class="btn btn-default">暂停</button>
<button id="restart" class="btn btn-default">开始</button>
</div>
</div>
</body>
<!--业务js文件-->
<script>
var $btn = $('#ctlBtn');
var $thelist = $('#thelist');
var startDate;
// HOOK 这个必须要再uploader实例化前面
WebUploader.Uploader.register({
// 在文件发送之前执行
'before-send-file': 'beforeSendFile',
// 在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行
'before-send': 'beforeSend',
// 在文件所有分片都上传完后,且服务端没有错误返回后执行
"after-send-file": "afterSendFile"
}, {
beforeSendFile: function (file) {
startDate = new Date();
console.log("开始上传时间" + startDate)
console.log("beforeSendFile");
// Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。
var deferred = WebUploader.Deferred();
//1、计算文件的唯一标记MD5,用于断点续传
uploader.md5File(file, 0, 3 * 1024 * 1024).progress(function (percentage) {
// 上传进度
console.log('上传进度:', percentage);
getProgressBar(file, percentage, "MD5", "MD5");
}).then(function (val) { // 完成
console.log('File MD5 Result:', val);
file.md5 = val;
file.uid = WebUploader.Base.guid();
// 判断文件是否上传过,是否存在分片,断点续传
$.ajax({
type: "POST",
url: "bigfile/check",
async: false,
data: {
fileMd5: val
},
success: function (data) {
var resultCode = data.resultCode;
// 秒传
if(resultCode == -1){
// 文件已经上传过,忽略上传过程,直接标识上传成功;
uploader.skipFile(file);
file.pass = true;
}else{
//文件没有上传过,下标为0
//文件上传中断过,返回当前已经上传到的下标
file.indexcode = resultCode;
}
}, error: function () {
}
});
//获取文件信息后进入下一步
deferred.resolve();
});
return deferred.promise();
},
beforeSend: function (block) {
//获取已经上传过的下标
var indexchunk = block.file.indexcode;
var deferred = WebUploader.Deferred();