对象存储BOS

    Object管理

    上传Object

    最简单的上传

    • 基本流程

      1. 创建BosClient。
      2. 调用putObject()方法,可以通过如下四种方式上传Object:字符串、DataUrl、文件和blob对象的形式。
    • 示例代码

      function done(response) {
          // 上传完成
      }
      function fail(fail) {
          // 上传失败
      }
      
      // 以字符串形式上传
      client.putObjectFromString(bucket, object, 'hello world')
        .then(done)
        .catch(fail);
      
      // 以buffer形式上传
      var buffer = new Buffer('hello world'); client.putObject(bucket, object, buffer)
        .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的Http Header和自定义Meta数据

    SDK本质上是调用后台的HTTP接口,因此BOS服务允许用户自定义Http Header。同时也允许用户对要上传的Object添加自定义Meta信息。以putObjectFromFile()函数为例,可以用以下代码来处理:

    • 示例代码

      let options = {
        'Content-Length': <file.size>, // 添加http header
        '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信息
      }
      client.putObjectFromFile(bucket, object, <path-to-file>, options)
        .then(done)
        .catch(fail);

      注意:自定义Meta信息的key需要以x-bce-meta-开头。

    查看Bucket中的Object

    简单查询

    查看Bucket中Object列表。

    • 基本流程

      1. 创建BosClient。
      2. 执行listObjects()方法。
    • 示例代码

      client.listObjects(<bucketName>)
        .then(function (response) {
            var contents = response.body.contents;
            for (var i = 0, l = contents.length; i < l; i++) {
                console.log(contents[i].key);
            }
        })
        .catch(function (error) {
            // 查询失败
        });

      注意:

      • 默认情况下,如果Bucket中的Object数量大于1000,则只会返回1000个Object,并且返回结果中IsTruncated值为True,并返回NextMarker做为下次读取的起点。
      • 若想获取更多的Object,可以使用Marker参数分次读取,请参考扩展查询

    扩展查询

    用户可以通过设置listObjects参数来完成更多扩展查询操作设置。可以设置的扩展参数如下:

    参数名称 说明 默认值
    maxKeys 设定此次返回Object的最大个数,不可超过1000。 1000
    prefix 设定objectKey的前缀,前缀是指objectKey包含并以prefix的值作为开始。
    通常与delimiter配合在查询模拟文件夹中使用。
    -
    delimiter 是一个分隔符,用来对objectKey进行分层。
    通常与prefix配合在查询模拟文件夹中使用。
    从prefix开始到第一次出现delimiter字符之间的objectKey称为:commonPrefixes。
    -
    marker 是一个字符串,用来设定返回结果的起始位置。
    设定marker值之后,返回的Object会从marker值之后按字母排序开始返回。
    -
    • 示例代码

      // 设置参数
      var options = {
          delimiter: '/',
          marker: '123'
      };
      
      client.listObjects(<bucketName>, options)
          .then(function (response) {
              var contents = response.body.contents;
              for (var i = 0, l = contents.length; i < l; i++) {
                  console.log(contents[i].key);
              }
          })
          .catch(function (error) {
              // 查询失败
          });

    查询模拟文件夹

    由于BOS本身是一个(<Key>,<Value>)的存储系统,所以原则上并不会存在“文件夹”的概念,但您可以通过 delimiterprefix 参数的配合进行文件夹功能模拟。

    假设Bucket中有5个文件:bos.jpg,fun/,fun/test.jpg,fun/movie/001.avi,fun/movie/007.avi,可以把 “/” 符号作为分隔符模拟文件夹。

    递归列出模拟文件夹下所有文件

    可以通过设置 Prefix 参数来获取某个模拟文件夹下所有的文件:

    // 设置参数
    let options = {
    prefix: 'fun/' // 递归列出fun目录下的所有文件
    };
    
    
    client.listObjects(<bucketName>, options)
    .then(function (response) {
        console.log('Objects:');
        var contents = response.body.contents;
        for (var i = 0, l = contents.length; i < l; i++) {
            console.log(contents[i].key);
        }
    })
    .catch(function (error) {
        // 查询失败
    });

    输出:

    Objects:
    fun/
    fun/movie/001.avi
    fun/movie/007.avi
    fun/test.jpg

    查看模拟文件夹下的文件和子文件夹

    PrefixDelimiter 结合的情况下,可以列出模拟文件夹下的文件和子文件夹:

    // 设置参数
    let options = {
        prefix: 'fun/', // 列出fun目录下的所有文件和文件夹
        delimiter: '/' // "/" 为文件夹的分隔符
    };
    
    client.listObjects(<bucketName>, options)
    .then(function (response) {
        console.log('Objects:');
        var contents = response.body.contents;
        for (var i = 0, l = contents.length; i < l; i++) {
            console.log(contents[i].key);
        }
        console.log('CommonPrefixs:');
        var commonPrefixes = response.body.commonPrefixes;
        for (i = 0, l = commonPrefixes.length; i < l; i++) {
            console.log(commonPrefixes[i]);
        }
    })
    .catch(function (error) {
        // 查询失败
    });

    输出:

    Objects:
    fun/
    fun/test.jpg
    
    CommonPrefixs:
    fun/movie/

    说明: 返回的结果中, Objects 的列表中给出的是fun文件夹下的文件。而 CommonPrefixs 的列表中给出的是fun文件夹下的所有子文件夹。可以看出 fun/movie/001.avifun/movie/007.avi 两个文件并没有被列出来,因为它们属于 fun 文件夹下的 movie 子文件夹下的文件。

    获取Object(仅支持Node.js)

    简单的获取Object

    用户可以通过如下代码将Object读取到一个流中。

    • 基本流程

      1. 创建BosClient。
      2. 执行getObject()。
    • 示例代码

      let range = '0-100';
      client.getObject(<BucketName>, <Key>, range)
          .then(function(response) {
              let buffer = response.body;
          });

      说明: 设置range为0-100表示只获取0到100字节的数据,您可以用此功能实现文件的分段下载和断点续传。如果不设置range,则获取整个Object。

    下载Object到指定路径

    用户可以通过如下代码直接将Object下载到指定路径。

    • 基本流程

      1. 创建BosClient。
      2. 执行client.getObject( )操作。
      3. Object可以直接下载到指定路径。
    • 示例代码

      let range = '0-100';
      client.getObjectToFile(<bucketName>, <key>, <filePath>, range)
          .then(function() {
              // 下载完成
          });

      说明: 设置range为0-100表示只获取0到100字节的数据,您可以用此功能实现文件的分段下载和断点续传。如果不设置range,则获取整个Object。

    只获取ObjectMetadata

    通过 getObjectMetadata() 方法可以只获取ObjectMetadata而不获取Object的实体。

    • 示例代码

      client.getObjectMetadata(<BucketName>, <ObjectKey>)
        .then(function(response) {
            console.dir(response.http_headers);
        });

    删除Object

    • 基本流程

      1. 创建BosClient。
      2. 执行deleteObject()方法。
    • 示例代码

      // 删除Object
      //指定要删除的Object所在Bucket名称和该Object名称
      client.deleteObject(<BucketName>, <ObjectKey>);   
    • 完整示例

      • 浏览器端: let BosClient = baidubce.sdk.BosClient
      • Node.js: import {BosClient} from '@baiducloud/sdk'
      let config = {
          endpoint: <EndPoint>,            //Bucket所在区域域名
          credentials: {
              ak: <AccessKeyID>,           //您的AK
              sk: <SecretAccessKey>       //您的SK
          }
      };
      
      let client = new BosClient(config);
      
      client.deleteObject(<BucketName>, <ObjectKey>);

    拷贝Object

    简单拷贝Object

    • 基本流程

      1. 创建BosClient。
      2. 执行copyObject( )方法。
    • 示例代码

      let options = {
        x-bce-meta-foo1: 'bar1', // 覆盖自定义meta信息
        x-bce-meta-foo2: 'bar2', // 覆盖自定义meta信息
        x-bce-meta-foo3: 'bar3' // 覆盖自定义meta信息
      }
      //SrcBucketName, SrcKey为原地址,DestBucketName, DestKey为拷贝到的目的地址
      client.copyObject(<SrcBucketName>, <SrcKey>, <DestBucketName>, <DestKey>, options);

    Object的分块上传

    除了通过putObject()方法上传文件到BOS以外,BOS还提供了另外一种上传模式:分块上传(Multipart Upload)。用户可以在如下的应用场景内(但不仅限于此),使用分块上传模式,如:

    • 需要支持断点上传。
    • 上传超过5GB大小的文件。
    • 网络条件较差,和BOS的服务器之间的连接经常断开。
    • 需要流式地上传文件。
    • 上传文件之前,无法确定上传文件的大小。

    分块上传比直接上传稍微复杂一点,分块上传需要分为三个阶段:

    • 开始上传(initiateMultipartUpload)
    • 上传分块(uploadPartFromBlob)
    • 上传完成(completeMultipartUpload)

    浏览器端代码示例

    对文件进行分块

    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 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 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,
                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) {
            return client.uploadPartFromFile(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, task.file , task.start)
                .then(function(res) {
                    ++state.loaded;
                    callback(null, res);
                })
                .catch(function(err) {
                    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);
            }
        });
    上一篇
    Bucket管理
    下一篇
    版本变更记录