阿里OSS大文件支持刷新的断点续传
在使用OSS时经常会有大文件上传的需求,然而大文件上传经常会遇到因网络原因造成的上传失败,结果就是辛辛苦苦上传了半天的东西失败了,还得重新上传。阿里云OSS提供的分片上传(Multipart Upload)和断点续传功能,可以将要上传的文件分成多个数据块(OSS里又称之为Part)来分别上传,上传完成之后再调用OSS的接口将这些Part组合成一个Object来达到断点续传的效果。
这一应用例子阿里云已经将其整理成了demo上传的git:https://github.com/ali-sdk/ali-oss。
但是在这个demo中只是实现了正常的上传,停止,在上传的一个过程,如果刷新页面之后则会重新开始上传。那么下面我来说一下如何解决刷新后继续断点续传。
基本思路
先来说一下基本的实现思路。断点续传的原理其实是将文件分成多个数据块,然后分别来上传,完成之后在将其组合成一个文件,当上传了一部分之后,你可以发现在oss的文件管理里面碎片增加了,增加的这些碎片便是已经上传成功的部分。
我们首先要做的是向后台获取accessKeyId、accessKeySecret、stsToken、region、bucket和endpoint这些信息。前面几个不再说,demo里面都已经给出了,endpont这个参数在demo中没有给出,如果你的网站是http网站,这个参数可以不用写,但是如果你的网站是https的则该参数是需要必填的,否则会上传失败,因为其默认上传是以http方式上传,当你的网站是https的时候,他会在前面也添加https,但是aliyuncs.com后面你会发现多了一个80端口。解决访问就是添加endpoint参数,强制指定https即可。
获取到了这些信息之后要做的就是构建OSS对象了
const client = new OSS({//这里或者是使用 new OSS.Wrapper({})的方式,这两种方式的区别是什么暂时没有弄清楚
region,
accessKeyId: creds.AccessKeyId,
accessKeySecret: creds.AccessKeySecret,
stsToken: creds.SecurityToken,
bucket,
endpoint:"https://"+region+".aliyuncs.com"
})
在构造好的这个对象中有一个listUploads接口,通过“prefix”可以获取到目前上传列表中的状态,prefix其实就是我们上传的那个key,比如我们要上传到"oecom/cn/123.jpg",则这个prefix就是"oecom/cn/123.jpg"。那么我们是如何知道即将上传的文件是哪个文件呢,在这里就涉及到了刷新断点续传的一个关键了,那就是将文件上传进度保存到localstorage中。
我们使用文件名加文件大小做md5加密之后为key来存储上传进度,只要在locslstorage中发现了这个key,就说明之前没有上传完成,取出其进度,在此进度之上继续上传即可
具体实现
我们先来说一下所需要的全局变量
let OSS = OSS.Wrapper;//oss对象
let uploadFileClient,currentCheckpoint;//分别是实例化后的client和当前上传进度
let retryCount = 0;//当前重试次数,oss因网络原因上传失败后,重新发起上传请求,只会重试一定次数
const retryCountMax = 3;//最多重试3次
var upload_oss = {//用来存储后台生成的上传文件路径和文件的完整url
fileKey : "",
fileUrl : ""
}
然后则是建立OSS连接,也就是我们的主方法:
var applyTokenDo = function (func, inputFile) {
var filename = inputFile[0].files[0].name;
var fileSize = inputFile[0].files[0].size;
var fileInfo = JSON.parse(localStorage.getItem(ossMd5(filename+fileSize)))
var fileKey = "";
if(fileInfo){
var pos = fileInfo.name.lastIndexOf(".");
var suffix = (pos >= 0) ? fileInfo.name.substring(0,pos) : fileInfo.name;
fileKey = suffix
if(fileKey){
fileKey = "?fileKey="+fileKey;
}else{
fileKey = "";
}
}
var url = '/api/checkUploadFileInfo' + fileKey;//文件的上传路径有后台定义,文件名随机生成
$.get(url, function (cr) {
upload_oss.fileKey = cr.fileKey;
upload_oss.fileUrl = cr.fileUrl;
//cr是后台已经拼接好的参数列表,这里直接使用,额外增加了endpont参数
cr.endpoint = "https://"+cr.region+".aliyuncs.com"
var client = new OSS(cr);
func(client, inputFile);
});
};
在这个方法中需要传递两个参数,一个是上传文件的函数,一个是上传文件域的input对象,我们在此处聪localStorage中获取上传文件的上传情况,如果之前是上传了一部分,则上传文件的路径不需要重新生成,为了避免文件名称重复而导致的文件覆盖问题,后台的操作策略是采用30位随机字符串作为文件名称。所以如果之前已经存在了,则将存储在localStorage中的文件路径取出来,传给后台,后台发现有这个参数,则直接将此参数返回,不自动生成。对于存储的这个对象具体存储了什么我们后面会详细说。
前面的准备工作基本上做的差不多了,下面就是主要的上传和上传检测了:
var uploadFile = function (client, inputFile) {
var file = inputFile[0].files[0];
if (!uploadFileClient || Object.keys(uploadFileClient).length === 0) {
uploadFileClient = client;
}
//取出文件后缀
var fileName = file.name;
var fileSize = file.size;
var pos = fileName.lastIndexOf(".");
var suffix = (pos >= 0) ? fileName.substring(pos) : "";
let options = {
progress,//这里的progress是上传进度的方法,用于更新进度条和获取当前的上传进度,下面会有介绍
partSize: 500 * 1024,//分片的大小
timeout: 60000
};
upload_oss.fileKey += suffix;
upload_oss.fileUrl += suffix;
uploadFileClient.listUploads({"prefix":upload_oss.fileKey}).then(res=> {
if(res.nextUploadIdMarker){
let theCheckPoint = JSON.parse(localStorage.getItem(ossMd5(fileName+fileSize)));
currentCheckpoint = theCheckPoint;
currentCheckpoint.file = file;
}
if (currentCheckpoint) {
options.checkpoint = currentCheckpoint;
}
return uploadFileClient.multipartUpload( upload_oss.fileKey, file,options).then(function (uploadRes) {
if(currentCheckpoint){
localStorage.removeItem(ossMd5(currentCheckpoint.file.name+currentCheckpoint.file.size))
}
currentCheckpoint = null;
uploadFileClient = null;
client_callback.pushUrl(upload_oss, inputFile, uploadRes);
}).catch((err) => {
if (uploadFileClient && uploadFileClient.isCancel()) {
console.log('stop-upload!');
} else {
console.error(err);
console.log(`err.name : {err.name}`);
console.log(`err.message :{err.message}`);
if (err.name.toLowerCase().indexOf('connectiontimeout') !== -1) {
// timeout retry
if (retryCount < retryCountMax) {
retryCount++;
console.error(`retryCount : ${retryCount}`);
uploadFile('');
}
}
}
});
});
};
这个uploadFile方法就是applyTokenDo方法传入的第一个参数,uploadFile方法前面的一些不再赘述,单说listUploads方法,通过传递路径参数,来获取未完成的文件信息,我们来看一下下图,下图所示的是其返回结果
当这个文件确实没有完成的时候nextUploadIdMarker就是其uploadID,如果没有上传过或已经上传成功,返回结果是空的,我们这里判断nextUploadIdMarker是否存在来表示是否已经上传了一部分,如果是,则从localStorage中取出上传进度,并将当前的文件对象赋值给它,因为checkpoint序列化存入后file对象会丢失,所以这里要重新赋值一下。当上传成功后将localstorage的key移除即可。client_callback方法是业务代码中添加的一个回调函数,和断点续传没有什么关系,可以忽略
下面来看一下进度条:
//更新进度条
var progress = function (p,checkpoint) {
if(checkpoint){
var checkOnlyKey = ossMd5(checkpoint.file.name+checkpoint.fileSize);
localStorage.setItem(checkOnlyKey,JSON.stringify(checkpoint))
}
console.log(checkpoint)
currentCheckpoint = checkpoint;
return function (done) {
//更新上传进度条
var bar = $("#uploadProcess");
var showText = Math.floor(p * 100) + '%';
//bar.style.width = showText;
bar.html(showText);
done();
}
};
更新进度条方法就不在赘述了。这里来看一下checkpoint的结构吧:
里面的name就是上传文件的路径,其他的参数都是可以看出代表什么的就不再说了。
整个oss大文件支持刷新断点续传基本上就是这些了。
请问下 ossmd5()怎么来的
ossmd5其实就是md5加密算法,很抱歉这里没有说明白,可以随便在网上找一个md5库即可