我开通了一个微信公共号“王和阳的航海日志”,在上面记录着自己的学习、思考、实践和成长的过程,欢迎关注、交流和拍砖。
明哥从办公室里发出一声怒吼:“这阿里云OSS服务的上传功能也太难用了,没有批量重命名功能,难道这几百个文件要我一个个手动重命名??!!”然后我和明哥就花了半天时间研究了下阿里云 SDK for JavaScript,顺利把文件上传功能集成到咱们的网站里了。
首先明确这次的需求:
接着去看一看阿里开放云存储OSS的介绍和基础概念 在申请了服务之后,阿里会提供一个bucket(可以理解为在阿里云上你上传的文件所在的文件夹名),同时拥有有对应的 Access Key ID & Access Key Secret (用于加密),阿里云把每一个文件当做Object对待,我们在浏览器端使用POST方式来上传文件,对于用Node.js上传文件,阿里在GitHub上提供了一个例子,基本上参考这个例子就可以写出完整的上传功能了。地址如下:https://github.com/aliyun-UED
首先我们要用npm安装阿里云SDK:
npm install aliyun-sdk
接下来需要添加aliyun-sdk.min和oss-js-upload这两个js文件,前者能在浏览器端调用aliyun sdk,后者对文件上传进行了一系列的封装,在这里我们对oss-js-upload.js文件进行了改造,使其增加了显示上传进度的功能,这是本文的重点所在,具体改动详见源代码文件,一开始根本不知道如何显示进度,但是在调试GitHub上阿里云团队提供的例子时,发现后台会打印出诸如 “Completed part 1”、”Completed part 2” 的信息,联系到Http POST请求的特征(一次最多传2M左右的文件),所以说在oss-js-upload中就是使用Mutilpart方法进行上传的,那么我们只要获取到各个块的上传进度,除以总的块数,不就可以显示上传进度了么?关键的思路就在这里!想通了这一点,那么接下来就好办多了!
最后贴上源代码供大家参考,重点需要注意的地方我已经加了标注,如果再有什么不懂的可以在评论区给我留言。
upload.js
/**
* Created by Young on 2015/8/28.
* [email protected]
*/
//阿里云提供功能的上传类
var ossUpload = new OssUpload({
bucket: 'keju-video',
// 选择杭州的 oss 实例所在地区选择填入,这里选的是
endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
// 如果文件大于 chunkSize 则分块上传, chunkSize 不能小于 100KB 即 102400
chunkSize: 1048576,
// 分块上传的并发数
concurrency: 5,
aliyunCredential: {
"accessKeyId": "在阿里云申请的accessKeyId",
"secretAccessKey": "在阿里云申请的secretAccessKey"
},
stsToken: null
});
var videoMp4;
var folderName;
var lessonId = "将要传的文件的ID以某种方式传入即可"; //获取要上传视频的ID,便于填写文件名
//检查视频文件的后缀名
String.prototype.endsWith = function (suffix) {
return !!this.match(suffix + "$");
};
var uploadVideo = function (type, file, fnProgress, fnSuccess, fnFail) {
var video = file;
var progress = 0;
ossUpload.upload({
// 必传参数, 需要上传的文件对象
file: video,
// 必传参数, 文件上传到 oss 后的名称, 包含路径
key: type + '/' + folderName + '/' + lessonId + '.' + type,
// 上传失败后重试次数
maxRetry: 3,
headers: {
'CacheControl': 'public',
'Expires': '',
'ContentEncoding': '',
'ContentDisposition': '',
// oss 支持的 header, 目前仅支持 x-oss-server-side-encryption
'ServerSideEncryption': ''
},
// 文件上传失败后调用, 可选参数
onerror: function (evt) {
console.log("error");
console.log(evt);
fnFail();
},
// 文件上传时的进度,每完成一片更新一次
onprogress: function (loaded, total) {
console.log(loaded, total, (loaded / total * 100).toFixed(1) + "%");
progress = (loaded / total * 100).toFixed(1) + "%";
fnProgress(progress);
},
// 文件上传成功调用, 可选参数
oncomplete: function (res) {
console.log("success");
console.log(res);
fnSuccess();
}
});
};
//选择上传的MP4文件
$("#btnSelectMp4").click(function () {
$("#mp4File")
.trigger("click")
.change(function (evt) {
//得到上传的文件对象
videoMp4 = evt.target.files[0];
folderName = $("#inputFolderName").val();
if (videoMp4.name.endsWith('mp4')) {
$("#btnUploadMp4").show();
} else {
return $("#tipNotMp4").show();
}
});
});
//点击开始上传Mp4文件
$("#btnUploadMp4").click(function () {
var btnUploadMp4 = $("#btnUploadMp4");
btnUploadMp4.prop('disabled', true);
btnUploadMp4.text("正在上传");
uploadVideo('mp4', videoMp4, function (progress) {
console.log("the progress is " + progress);
btnUploadMp4.text(progress);
}, function () {
btnUploadMp4.prop('disabled', false);
btnUploadMp4.hide();
btnUploadMp4.text('上传.mp4文件');
$("#uploadMp4Succeed").show();
$("#btnSelectMp4").show();
$('#mp4Address').val('');
$("#mp4File").replaceWith($("#mp4File").val('').clone(true));
}, function () {
//上传如果失败,则再次显示按钮让用户重新上传
btnUploadMp4.prop('disabled', false);
btnUploadMp4.text('上传.mp4文件');
btnUploadMp4.hide();
$("#uploadMp4Failed").show();
$("#btnSelectMp4").show();
$('#mp4Address').val('');
$("#mp4File").replaceWith($("#mp4File").val('').clone(true));
});
});
oss-js-upload.js
'use strict';
(function () {
var detectIEVersion = function () {
var v = 4,
div = document.createElement('div'),
all = div.getElementsByTagName('i');
while (
div.innerHTML = '',
all[0]
) {
v++;
}
return v > 4 ? v : false;
};
var _extend = function (dst, src) {
for (var i in src) {
if (Object.prototype.hasOwnProperty.call(src, i) && src[i]) {
dst[i] = src[i];
}
}
};
function OssUpload(config) {
if (!config) {
// console.log('需要 config');
return;
}
this._config = {
chunkSize: 1048576 // 1MB
};
if (this._config.chunkSize && this._config.chunkSize < 102400) {
// console.log('chunkSize 不能小于 100KB');
return;
}
_extend(this._config, config);
if (!this._config.aliyunCredential && !this._config.stsToken) {
// console.log('需要 stsToken');
return;
}
if (!this._config.endpoint) {
// console.log('需要 endpoint');
return;
}
var ALY = window.ALY;
if (this._config.stsToken) {
this.oss = new ALY.OSS({
accessKeyId: this._config.stsToken.Credentials.AccessKeyId,
secretAccessKey: this._config.stsToken.Credentials.AccessKeySecret,
securityToken: this._config.stsToken.Credentials.SecurityToken,
endpoint: this._config.endpoint,
apiVersion: '2013-10-15'
});
}
else {
this.oss = new ALY.OSS({
accessKeyId: this._config.aliyunCredential.accessKeyId,
secretAccessKey: this._config.aliyunCredential.secretAccessKey,
endpoint: this._config.endpoint,
apiVersion: '2013-10-15'
});
}
var arr = this._config.endpoint.split('://');
if (arr.length < 2) {
// console.log('endpoint 格式错误');
return;
}
this._config.endpoint = {
protocol: arr[0],
host: arr[1]
}
}
OssUpload.prototype.upload = function (options) {
if (!options) {
if (typeof options.onerror == 'function') {
options.onerror('需要 options');
}
return;
}
if (!options.file) {
if (typeof options.onerror == 'function') {
options.onerror('需要 file');
}
return;
}
var file = options.file;
if (!options.key) {
if (typeof options.onerror == 'function') {
options.onerror('需要 key');
}
return;
}
// 去掉 key 开头的 /
options.key.replace(new RegExp("^\/"), '');
var self = this;
var readFile = function (callback) {
var result = {
chunksHash: {},
chunks: []
};
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
var chunkSize = self._config.chunkSize;
var chunksNum = Math.ceil(file.size / chunkSize);
var currentChunk = 0;
var frOnload = function (e) {
result.chunks[currentChunk] = e.target.result;
currentChunk++;
if (currentChunk < chunksNum) {
loadNext();
}
else {
result.file_size = file.size;
callback(null, result);
}
};
var frOnerror = function () {
console.error("读取文件失败");
if (typeof options.onerror == 'function') {
options.onerror("读取文件失败");
}
};
function loadNext() {
var fileReader = new FileReader();
fileReader.onload = frOnload;
fileReader.onerror = frOnerror;
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
var blobPacket = blobSlice.call(file, start, end);
fileReader.readAsArrayBuffer(blobPacket);
}
loadNext();
};
var uploadSingle = function (result, callback) {
var params = {
Bucket: self._config.bucket,
Key: options.key,
Body: result.chunks[0],
ContentType: file.type || ''
};
_extend(params, options.headers);
self.oss.putObject(params, callback);
};
var uploadMultipart = function (result, callback) {
var maxUploadTries = options.maxRetry || 3;
var uploadId;
var loadedNum = 0;
var latestUploadNum = -1;
var concurrency = 0;
var multipartMap = {
Parts: []
};
``` javascript
//这里使用arguments读取传入的onProgres函数并回调
if(3===arguments.length){
var fnProgress = arguments[2];
}
```
var init = function () {
var params = {
Bucket: self._config.bucket,
Key: options.key,
ContentType: file.type || ''
};
_extend(params, options.headers);
self.oss.createMultipartUpload(params,
function (mpErr, res) {
if (mpErr) {
// console.log('Error!', mpErr);
callback(mpErr);
return;
}
// console.log("Got upload ID", res.UploadId);
uploadId = res.UploadId;
uploadPart(0);
});
};
var uploadPart = function (partNum) {
if(partNum >= result.chunks.length) {
return;
}
concurrency++;
if(latestUploadNum < partNum) {
latestUploadNum = partNum;
}
if(concurrency < self._config.concurrency && (partNum < (result.chunks.length - 1))) {
uploadPart(partNum + 1);
}
var partParams = {
Body: result.chunks[partNum],
Bucket: self._config.bucket,
Key: options.key,
PartNumber: String(partNum + 1),
UploadId: uploadId
};
var tryNum = 1;
var doUpload = function () {
self.oss.uploadPart(partParams, function (multiErr, mData) {
if (multiErr) {
// console.log('multiErr, upload part error:', multiErr);
if (tryNum > maxUploadTries) {
console.log('上传分片失败: #', partParams.PartNumber);
callback(multiErr);
}
else {
console.log('重新上传分片: #', partParams.PartNumber);
tryNum++;
doUpload();
}
return;
}
// console.log(mData);
concurrency--;
multipartMap.Parts[partNum] = {
ETag: mData.ETag,
PartNumber: partNum + 1
};
console.log("Completed part", partNum + 1);
//console.log('mData', mData);
loadedNum++;
``` JavaScript
//回调upload.js文件中写的onprogress函数,显示进度
if("function" === typeof fnProgress){
fnProgress(loadedNum, result.chunks.length);
}
```
if (loadedNum == result.chunks.length) {
complete();
}
else {
uploadPart(latestUploadNum + 1);
}
});
};
doUpload();
};
var complete = function () {
// console.log("Completing upload...");
var doneParams = {
Bucket: self._config.bucket,
Key: options.key,
CompleteMultipartUpload: multipartMap,
UploadId: uploadId
};
self.oss.completeMultipartUpload(doneParams, callback);
};
init();
};
readFile(function (err, result) {
var callback = function (err, res) {
if (err) {
if (typeof options.onerror == 'function') {
options.onerror(err);
}
return;
}
if (typeof options.oncomplete == 'function') {
options.oncomplete(res);
}
};
if (result.chunks.length == 1) {
uploadSingle(result, callback)
}
else {
//若文件大于2MB,使用分块上传,显示进度,若小于2MB,则不显示进度直接上传
if('function' === typeof options.onprogress){
uploadMultipart(result, callback, options.onprogress);
} else {
uploadMultipart(result, callback);
}
}
});
};
window.OssUpload = OssUpload;
})();