上传Object
在BOS中,用户操作的基本数据单元是Object。Bucket中的Object数量不限,但单个Object最大允许存储5TB的数据。
Object包含Key、Meta和Data。其中:
- Key是Object的名字;
- Meta是用户对该Object的描述,由一系列Name-Value对组成;
- Data是Object的数据。
BOS JavaScript SDK提供了丰富的文件上传接口,可以通过以下方式上传文件:
- 简单上传
- 追加上传
- 分块上传
- 断点续传上传
Object的命名规范如下:
- 使用UTF-8编码。
- 长度必须在1-1023字节之间。
- 首字母不能为'/',不能包含'@'字符,'@'用于图片处理接口。
简单上传
在简单上传的场景中,JS SDK 支持以数据流方式、以字符串方式、以指定文件形式(仅支持Node.js环境)、以blob对象形式(仅支持浏览器环境)执行Object上传。 分别对应 putObject 、 putObjectFromString 、putObjectFromFile 、putObjectFromBlob 方法。
-
基本流程
- 创建BosClient。
- 调用putObject()相关方法
-
示例代码
function done(response) { // 上传完成 } function fail(fail) { // 上传失败 } // 以buffer形式上传 var buffer = new Buffer('hello world'); client.putObject(bucket, object, buffer) .then(done) .catch(fail); // 以字符串形式上传 client.putObjectFromString(bucket, object, 'hello world') .then(done) .catch(fail); // 以文件形式上传,仅支持Node.js环境 client.putObjectFromFile(bucket, object, <path-to-file>) .then(done) .catch(fail); // 以blob对象形式上传,仅支持浏览器环境 client.putObjectFromBlob(bucket, object, <blob对象>) .then(done) .catch(fail);
说明:Object以文件的形式上传到BOS中,putObject函数支持不超过5GB的Object上传。在putObject请求处理成功后,BOS会在Header中返回Object的ETag作为文件标识。
追加上传
上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。
正因如此,百度智能云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。
let bucketName = "yourbucket";
let appendKey = "appendObjectKey";
// 首次上传时,offset设置为null
client.appendObjectFromString(bucketName,appendKey,"firstContent",null)
.then(function(response){
// 从响应头中获取offset偏移量
var offset = + response.http_headers['x-bce-next-append-offset'];
// 第二次追加上传时,指定上面获取到的offset传入
client.appendObjectFromString(bucketName, appendKey, "appendContent", offset);
});
分块上传
除了通过putObject()方法上传文件到BOS以外,BOS还提供了另外一种上传模式:分块上传(Multipart Upload)。用户可以在如下的应用场景内(但不仅限于此),使用分块上传模式,如:
- 需要支持断点上传。
- 上传超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
分块上传比直接上传稍微复杂一点,分块上传需要分为三个阶段:
- 开始上传(initiateMultipartUpload)
- 上传分块(uploadPartFromBlob)
- 上传完成(completeMultipartUpload)
浏览器端代码示例
对文件进行分块
let options = {
'Content-Type': 'application/json', // 添加http header
'Cache-Control': 'public, max-age=31536000', // 指定缓存指令
'Content-Disposition': 'attachment; filename="example.jpg"', // 指示回复的内容该以何种形式展示
'x-bce-meta-foo1': 'bar1', // 添加自定义meta信息
'x-bce-meta-foo2': 'bar2', // 添加自定义meta信息
'x-bce-meta-foo3': 'bar3' // 添加自定义meta信息
};
let PART_SIZE = 5 * 1024 * 1024; // 指定分块大小
function getTasks(file, uploadId, bucketName, key) {
let leftSize = file.size;
let offset = 0;
let partNumber = 1;
let tasks = [];
while (leftSize > 0) {
let partSize = Math.min(leftSize, PART_SIZE);
tasks.push({
file: file,
uploadId: uploadId,
bucketName: bucketName,
key: key,
partNumber: partNumber,
partSize: partSize,
start: offset,
stop: offset + partSize - 1
});
leftSize -= partSize;
offset += partSize;
partNumber += 1;
}
return tasks;
}
处理每个分块的上传逻辑
function uploadPartFile(state, client) {
return function(task, callback) {
let blob = task.file.slice(task.start, task.stop + 1);
client.uploadPartFromBlob(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, blob)
.then(function(res) {
++state.loaded;
callback(null, res);
})
.catch(function(err) {
callback(err);
});
};
}
初始化uploadID,开始上传分块,并完成上传
let uploadId;
client.initiateMultipartUpload(bucket, key, options)
.then(function(response) {
uploadId = response.body.uploadId; // 开始上传,获取服务器生成的uploadId
let deferred = sdk.Q.defer();
let tasks = getTasks(blob, uploadId, bucket, key);
let state = {
lengthComputable: true,
loaded: 0,
total: tasks.length
};
// 为了管理分块上传,使用了async(https://github.com/caolan/async)库来进行异步处理
let THREADS = 2; // 同时上传的分块数量
async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(results);
}
});
return deferred.promise;
})
.then(function(allResponse) {
let partList = [];
allResponse.forEach(function(response, index) {
// 生成分块清单
partList.push({
partNumber: index + 1,
eTag: response.http_headers.etag
});
});
return client.completeMultipartUpload(bucket, key, uploadId, partList); // 完成上传
})
.then(function (res) {
// 上传完成
})
.catch(function (err) {
// 上传失败,添加您的代码
console.error(err);
});
Node.js端代码示例
对文件进行分块,并初始化UploadID,上传分块
let options = {
'Content-Type': 'application/json', // 添加http header
'Cache-Control': 'public, max-age=31536000', // 指定缓存指令
'Content-Disposition': 'attachment; filename="example.jpg"', // 指示回复的内容该以何种形式展示
'x-bce-meta-foo1': 'bar1', // 添加自定义meta信息
'x-bce-meta-foo2': 'bar2', // 添加自定义meta信息
'x-bce-meta-foo3': 'bar3' // 添加自定义meta信息
};
let PART_SIZE = 5 * 1024 * 1024; // 指定分块大小
let uploadId;
client.initiateMultipartUpload(bucket, key, options)
.then(function(response) {
uploadId = response.body.uploadId; // 开始上传,获取服务器生成的uploadId
let deferred = sdk.Q.defer();
let blob = {
// 使用fs文件库获取文件大小
size: fs.statSync(localFileName).size,
filename: localFileName
}
let tasks = getTasks(blob, uploadId, bucket, key);
let state = {
lengthComputable: true,
loaded: 0,
total: tasks.length
};
// 为了管理分块上传,使用了async(https://github.com/caolan/async)库来进行异步处理
let THREADS = 2; // 同时上传的分块数量
async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(results);
}
});
return deferred.promise;
})
.then(function(allResponse) {
let partList = [];
allResponse.forEach(function(response, index) {
// 生成分块清单
partList.push({
partNumber: index + 1,
eTag: response.http_headers.etag
});
});
return client.completeMultipartUpload(bucket, key, uploadId, partList); // 完成上传
})
.then(function (res) {
// 上传完成
})
.catch(function (err) {
// 上传失败,添加您的代码
console.error(err);
});
function getTasks(file, uploadId, bucketName, key) {
let leftSize = file.size;
let offset = 0;
let partNumber = 1;
let tasks = [];
while (leftSize > 0) {
let partSize = Math.min(leftSize, PART_SIZE);
tasks.push({
file: file.filename,
uploadId: uploadId,
bucketName: bucketName,
key: key,
partNumber: partNumber,
partSize: partSize,
start: offset,
stop: offset + partSize - 1
});
leftSize -= partSize;
offset += partSize;
partNumber += 1;
}
return tasks;
}
function uploadPartFile(state, client) {
return function(task, callback) {
console.log("task: ", task)
return client.uploadPartFromFile(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, task.file , task.start)
.then(function(res) {
++state.loaded;
console.log("ok")
callback(null, res);
})
.catch(function(err) {
console.log("bad")
callback(err);
});
};
}
取消分块上传事件
用户可以使用abortMultipartUpload方法取消分块上传。
client.abortMultipartUpload(<BucketName>, <Objectkey>, <UploadID>);
获取未完成的分块上传事件
用户可以使用listMultipartUploads方法获取Bucket内未完成的分块上传事件。
client.listMultipartUploads(<bucketName>)
.then(function (response) {
// 遍历所有上传事件
for (var i = 0; i < response.body.multipartUploads.length; i++) {
console.log(response.body.multipartUploads[i].uploadId);
}
});
获取所有已上传的块信息
用户可以使用listParts方法获取某个上传事件中所有已上传的块。
client.listParts(<bucketName>, <key>, <uploadId>)
.then(function (response) {
// 遍历所有上传事件
for (var i = 0; i < response.body.parts.length; i++) {
console.log(response.body.parts[i].partNumber);
}
});
断点续传上传
当用户向BOS上传大文件时,如果网络不稳定或者遇到程序崩等情况,则整个上传就失败了,失败前已经上传的部分也作废,用户不得不重头再来。这样做不仅浪费资源,在网络不稳定的情况下,往往重试多次还是无法完成上传。基于上述场景,BOS提供了断点续传上传的能力,主要利用了分块上传的能力,将待上传的文件拆分成多个分块(Part),然后分别上传这些分块,当所有分块全部上传完成后,BOS将请求者上传的所有分块组合成完整的Object。
putSuperObject
1.0.1-beta.2
及以上版本支持
JavaScript SDK提供了putSuperObject
方法,对分块上传相关的API做了高级封装,支持分上传任务暂停、恢复、取消、分片并发数设置,失败重试等功能。
请求头
除公共头域外,无其他特殊请求头
初始化参数
参数名称 | 说明 | 类型 | 必填 | 默认值 | 示例值 |
---|---|---|---|---|---|
bucketName | 存储桶名称 | string |
是 | - | "bucket001" |
objectName | 上传后对象名称 | string |
是 | - | "file001" |
data | 上传数据, 类型为string时表示文件路径,还支持Buffer和Blob对象 | string | Buffer | Blob |
是 | - | - |
StorageClass | 文件存储类型 | "STANDARD" | "STANDARD_IA" | "COLD" | "ARCHIVE" | "MAZ_STANDARD" | "MAZ_STANDARD_IA" |
否 | "STANDARD" |
"STANDARD" |
chunkSize | 上传文件分片默认体积, 单位为bytes | number |
否 | 5 * 1024 ** 2(5MB) | 1048576 |
partConcurrency | 分片并发数量 | number | 否 | 5 | 5 |
ContentLength | 文件大小,单位为bytes,非必填,内部会基于自动计算 | string |
否 | - | 1048576 |
ContentType | 文件媒体类型,非必填,内部会基于objectName字段自动生成 | string |
否 | - | "application/x-gtar" |
uploadId | 分片上传任务ID,非必填,当需要从断点恢复任务时,需要传入该参数 | string |
否 | - | "a44cc9bab11cbd156984767aad637851" |
onProgress | 上传进度回调函数 | ProgressCallback |
否 | - | - |
onStateChange | 任务状态变化回调函数 | StateChangeCallback |
否 | - | - |
ProgressCallback
type ProgressCallback = (
/* 当前上传速度 */
speed: string,
/* 上传进度,保留4位小数 */
progress: number,
/* 上传进度-百分比 */
percent: string,
/* 已上传字节数 */
uploadedBytes: number,
/* 文件总字节数 */
totalBytes
) => void
StateChangeCallback
type StateChangeCallback = (
/* 状态 */
state: string,
/* 如果上传失败会传递失败的message,上传成功会返回上传后的地址 */
options: {
message: string;
data: Record<string, any> | null
}
) => void;
状态
"inited"
:任务初始化完成"running"
:任务队列运行中"paused"
:任务队列暂停"completed"
:任务完成,上传结束"cancelled"
:任务取消,上传结束"failed"
:任务异常,上传结束
Node.js端代码示例
const sdk = require('@baiducloud/sdk');
const client = new sdk.BosClient({
endpoint: 'http://bj.bcebos.com',
credentials: {
ak: "<Your Access Key>",
sk: "<Your Secret Key>"
}
});
// 存储桶名称
const bucketName = "<Your Bucket Name>";
// 上传后文件名称
const objectName = 'demo.tgz';
// 本地文件路径
const data = '/Mock/path/to/local/file/demo.tgz';
// 初始化上传任务
const SuperUploadTask = client.putSuperObject({
// 桶名称
bucketName,
// 上传后对象名称
objectName,
// 上传数据, 类型为string时表示文件路径
data,
// 分片并发数
partConcurrency: 2,
// 上传进度回调函数
onProgress: (options) => {
const {speed, progress, percent, uploadedBytes, totalBytes} = options;
console.log(options);
},
// 状态变化回调函数
onStateChange: (state, data) => {
if (state === 'completed') {
console.log('上传成功');
} else if (state === 'failed') {
console.error('上传失败,失败原因:' + data.message);
} else if (state === 'cancelled') {
console.log('上传任务取消');
} else if (state === 'inited') {
console.log('上传任务初始化完成');
} else if (state === 'running') {
console.log('上传任务开始运行...');
} else if (state === 'paused') {
console.log('上传任务已暂停');
}
}
});
// 启动上传任务
const tasks = SuperUploadTask.start();
console.log('切分任务: ', tasks);
// 暂停上传任务
setTimeout(() => {
SuperUploadTask.pause();
}, 5000);
// 恢复上传任务
setTimeout(() => {
SuperUploadTask.resume();
}, 15000);
// 取消上传任务
setTimeout(async () => {
const result = SuperUploadTask.cancel();
console.log(result ? '任务取消成功' : '任务取消失败');
}, 25000);