0、写在前面的话
上篇博客已是在8月了,期间到底发生了什么,只有我本身知道,反正就是心情特别糟糕,生活状态工做状态学习状态都十分很差,还有心思进取吗,No!如今状态好起来了,生活又充满了但愿 :D
前两周在写视频管理相关的功能,说是要在原来的项目上进行拓展。结果今天领导给我说客户那边还没定,只作技术上研究就好了,不用写具体功能代码(我都写了好吗?)因而忽然时间有腾出来,今天整理一下把内容写一些。
要努力努力,为了更好的人为了更好的生活。
一、断点续传的两种方式
1.1 RandomAccessFile
客户端给一个已经上传的位置标记,而后服务器端就能够在指定的位置进行处理。这个断点位置的读取,就要用到RandomAccessFile类,该类不一样于InputStream和OutputStream,它既能够对文件进行读也能够进行写,两个重要方法:
- long getFilePoint():返回文件记录指针的当前位置,不指定指针的位置默认是0
- void seek(long pos):设置文件指针偏移,即将文件记录指针定位到pos位置
至于position位置如何去处理,就看各自的想法了。1)你能够将位置存在浏览器(好比localStorage),下次传输的时候前端从残缺位置切割文件 blob.slice() 只传输剩余的部分,后端直接接收接着写入服务器便可;2)也能够前端把文件完整传输,同时带上position参数,由后端经过 RandomAccessFile 在指定位置开始读取内容便可。
至于客户端和服务端之间文件的一致性,多使用md5进行校验。
1.2 分片处理
H5中新增了File API,能够经过使用 slice() 方法生成只有某段文件内容。这个方法就为断点续传提供了新的方式,就是分片处理,假设一个文件是100M大小,那么每次传输我只须要传送10M,按序发送10次请求便可。某个分片传送失败,那么从这个分片再继续发送便可,后端则对分片文件进行合并成完整文件。
其实方式和1.1提到的是相似的,不过每次传输的数据单位量更大一些,完整文件交给后端进行合并。
二、WebUploader的分片段点续传
WebUploader的选项中支持直接开启分片上传:
var uploader = WebUploader.Uploader({
swf: 'path_of_swf/Uploader.swf',
// 开起分片上传
chunked: true,
// 分片大小,默认5M
chunkSize: 5242880,
// 分片出错后(如网络缘由等形成出错)的重传次数
chunkRetry: 2,
// 上传并发数
threads: 1
});
开启分片上传后,插件会自动分片上传文件,接下来只须要在配置文件跳过和后端处理便可。官方回应在分片发送前会有监听的事件 uploadBeforeSend,在这个方法的callback里面若是返回的是一个promise,且此promise被reject了,那么此分片就跳过了。(
实际上该方式在自测和咨询网友时发现,并无什么用,即使按照官方说明,分片也没有跳过,仍然日后端进行了请求发送,同时也附带有文件)
webUploader.on('uploadBeforeSend', function(block, data){
data.fileMd5 = block.file.wholeMd5;
var deferred = WebUploader.Deferred();
var chunk = data.chunk;
var existChunks = block.file.existChunks;
//后端返回了已存在分片的数组,这里判断要发送的分片是否已存在
if(existChunks && existChunks.indexOf(chunk) != -1) {
//console.log("分片存在,已跳过:" + chunk);
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise();
});
分片是否存在的判断,也有不一样的方式,一种你能够每次计算分片的md5值发送给后端,若是服务器已存在则跳过,不然就发送;另外一种就是只向服务器查询一次获取已经存在的分片,而后在浏览器端进行比对,但如此须要考虑分片是否并发传输,进行相应处理。
我采用的方式是:先对文件进行md5计算,在服务器端建立和md5值同名的文件夹,每次上传的分片存放在对应文件夹,文件名即分片的序号,好比某文件夹中可能存在文件 0, 1, 2, 3... 前端发送分片前请求后端数据,后端将已经存在的分片名数组返回前端,前端进行跳过处理,同时后端在接收分片也要作是否存在的判断,已存在的话就再也不进行读写操做,直到最后分片到达,则进行分片的按序合并便可。
public boolean uploadChunk() throws ChunkUploadException {
HttpServletRequest request = ServletActionContext.getRequest();
//封装源文件信息
FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
//获取同时上传的文件其余属性
Map<String, String> params = getVideoParams(request);
if (params.get("fileMd5") == null || "".equals(params.get("fileMd5"))) {
throw new ChunkUploadException("文件md5值未传递");
}
//存放
File temp = new File(getTempPath(params.get("fileMd5")) + "/" + srcFileInfo.getCurChunk());
if (!temp.exists()) {
try {
VideoUtil.copy(srcFileInfo.getFile(), temp);
} catch (IOException e) {
throw new ChunkUploadException("分片上传失败: chunkNum" + params.get("chunk"));
}
}
//若是是最后分片
return !srcFileInfo.isChunked() || srcFileInfo.getCurChunk() == srcFileInfo.getChunkSize() - 1;
}
public String upload() {
boolean isLastChunk = false;
try {
isLastChunk = uploadChunk();
} catch (ChunkUploadException e) {
e.printStackTrace();
AjaxSupport.sendFailText(null, e.getMessage());
return AJAX_RESULT;
}
//不是最后的分片,直接返回成功响应
if (!isLastChunk) {
AjaxSupport.sendSuccessText("chunk uploaded", "success");
return AJAX_RESULT;
}
//最后切片
else {
HttpServletRequest request = ServletActionContext.getRequest();
//封装源文件信息
FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
//获取同时上传的文件其余属性
Map<String, String> params = getVideoParams(request);
//获取合并文件的文件名
String filename = UUID.randomUUID().toString() + "." + srcFileInfo.getFileType();
//合并文件
File tempDir = new File(getTempPath(params.get("fileMd5")));
File[] tempfileArr = tempDir.listFiles();
File storeFile = new File(getStorePath() + "/" + filename);
try {
VideoUtil.merge(tempfileArr, storeFile);
}
...
最后,实际上这种方式断点续传仍然存在不少细节没有考虑,好比多线程,同个浏览器两个tab发送同一文件时如何处理?
三、参考连接