Web端直传实践
所有文档

          对象存储 BOS

          Web端直传实践

          场景概述

          用户通过Web上传,是BOS使用需求中很典型的一种应用场景。在该场景下,用户通常采用应用服务器中转的模型进行文件上传。

          1. 用户先将文件通过Web上传到应用服务器;
          2. 应用服务器再将文件上传到BOS。

          在该模型中,存在如下3个缺点:

          • 上传速度慢。因为需要经过应用服务器中转,与客户端数据直传到BOS相比,网络传送增加了一倍。
          • 扩展性差。随着用户数量的增加,应用服务器可能成为传输瓶颈。
          • 成本高。应用服务器的部署和维护需要一定成本,如客户端数据直传到BOS,将节省应用服务器的开销,且BOS上传的流量是免费的。

          因此,在该场景下,我们推荐您使用bce-bos-uploader工具实现客户端直传BOS的方式。

          bce bos uploader

          Baidu Cloud Engine BOS Uploader(bce-bos-uploader)是百度智能云基于Javascript SDK开发的一个ui组件,为了方便用户开发web直传应用而专门提供的,使用该工具用很少的几行代码就可以完成跟BOS服务的对接。bce-bos-uploader的demo操作界面如下:

          支持的浏览器

          1. 基于Xhr2和File API,可以支持IE10+, Firefox,Chrome和Opera最新版。
          2. 借助PostObject接口,可以支持IE低版本(6,7,8,9),详细请参见进阶篇二:通过PostObject接口处理IE低版本

          支持的配置参数

          名称 是否必填 默认值 说明
          bos_bucket Y 需要上传到的Bucket
          browse_button Y 需要初始化的<input type="file"/>
          uptoken_url N 用来进行计算签名的URL,需要支持JSONP
          bos_endpoint N <http://bj.bcebos.com> BOS服务器的地址
          bos_ak N 如果没有设置uptoken_url的话,必须有ak和sk这个配置才可以工作
          bos_sk N 如果没有设置uptoken_url的话,必须有ak和sk这个配置才可以工作
          uptoken N 如果是临时的ak和sk,必须通过这个参数设置sts token的值
          multi_selection N false 是否可以选择多个文件
          auto_start N false 选择文件之后,是否自动上传
          max_file_size N 100M 可以选择的最大文件,超过这个值之后,会被忽略掉
          bos_multipart_min_size N 10M 超过这个值之后,采用分片上传的策略。如果想让所有的文件都采用分片上传,把这个值设置为0即可
          chunk_size N 4M 分片上传的时候,每个分片的大小(如果没有切换到分片上传的策略,这个值没意义)
          accept N 可以支持选择的文件类型,以逗号分割的后缀名,例如:txt,pdf,doc,docx
          flash_swf_url N mOxie Flash文件的地址。如果需要支持低版本的IE,必须设置这个参数

          更详细的使用信息请参阅:bce-bos-uploader说明

          bce-bos-upload支持默认、STS、PostObject三种签名方式。

          • 默认签名方式即使用AK/SK签名方式在浏览器中直接上传文件到BOS中。通过浏览器直传文件到BOS服务器的时候,如果把AK和SK暴露在页面中,会引发安全性的问题。 攻击者如果获取了AK和SK,可以对BOS上面的数据进行任意的操作,为了降低泄露的风险,建议用户使用STS临时认证。
          • 使用STS签名更安全灵活,它可以对用户的使用权限进行灵活、精确地控制,而且不必每次请求都调用后端接口,在有效期内就可以不用再请求新的Security Token。
          • 因为IE低版本(IE6,7,8,9)对 HTML5 API 支持的不完善,为了在这些浏览器里面实现文件直传的功能,BOS开发了PostObject接口,支持了 multipart/form-data 的请求格式,方便在低版本的IE下面把文件上传到BOS服务器。

          基础篇:在浏览器中直接上传文件到BOS

          使用 bce-bos-uploader,可以参考下面的内容完成如何在浏览器中直接上传文件到BOS。使用流程:

          1. 开启Bucket的跨域访问设置
          2. 查询ak/sk
          3. 初始化bce-bos-uploader参数

          开启Bucket的跨域访问

          受浏览器安全限制,如果想直接在浏览器中访问BOS服务,必须正确设置好相关bucket的跨域功能。设置方法如下:

          1. 登录百度智能云控制台。
          2. 选择Bucket并进入Bucket管理页面。
          3. 点击左侧『Bucket属性』,进入Bucket配置的页面。
          4. 点击右侧『CORS设置』,进入CORS设置页面。
          5. 点击『添加规则』按钮,可以添加一条或者多条CORS的规则。

          查询AK/SK

          在百度智能云控制台首页右上角账号下的“安全认证”查询AK和SK的信息,也可以在Bucket管理中查看。详细操作可参见管理ACCESSKEY

          获取bce-bos-uploader

          有两种方式可以获取bce-bos-uploader的代码:

          • 第一种:通过npm安装

            npm install @baiducloud/bos-uploader
          • 第二种:直接引用CDN上面的资源(测试专用,不建议用于生产环境)

            <script src=" https://bj.bcebos.com/v1/bce-cdn/lib/@baiducloud/bos-uploader/<version>/bce-bos-uploader.bundle.min.js"></script>

          初始化bce-bos-uploader

          <!doctype html>
          <html>
            <head>
              <meta charset="utf-8" />
              <title>bce-bos-uploader simple demo</title>
              <!--[if lt IE 8]><script src="https://unpkg.com/json3@3.3.2/lib/json3.min.js"></script><![endif]-->
              <!--[if lt IE 9]><script src="https://unpkg.com/js-polyfills@0.1.42/es5.js"></script><![endif]-->
              <!--[if lt IE 10]><script src="https://unpkg.com/mOxie@1.5.7/bin/js/moxie.min.js"></script><![endif]-->
              <script src="https://unpkg.com/jquery@3.3.1/dist/jquery.min.js"></script>
              <script src="https://bce.bdstatic.com/lib/@baiducloud/bos-uploader/1.4.0-rc.0/bce-bos-uploader.bundle.min.js"></script>
            </head>
            <body>
              <input type="file" id="file" >
              <button type="submit">开始上传</button>
              <script>
                var uploader = new baidubce.bos.Uploader({
                browse_button: '#file',
                bos_bucket: '<your bucket>',
                bos_endpoint: '<your host>',
                bos_ak: '<your ak>', 
                bos_sk: '<your sk>',
                max_file_size: '1Gb',
                init: {
                  FileUploaded: function (_, file, info) {
                    var bucket = info.body.bucket;
                    var object = info.body.object;
                    var url = '<your host>' + bucket + '/' + object;
                    $(document.body).append($('<div><a href="' + url + '">' + url + '</a></div>'));
                  },
                  UploadComplete: function() {
                    $(document.body).append('<div>上传结束!</div>');
                  }
                }
              });
               $('button[type=submit]').click(function () {
                uploader.start();
                return false;
              });
          
              </script>
            </body>
          </html>

          将上述代码保存为index.html,下面会启动webserver来访问这个页面

          启动webserver

          • 通过PHP来启动

            php -S 0.0.0.0:9999
          • 通过Python来启动

            python -m SimpleHTTPServer 9999
          • 其它方式请参考相关的文档

          启动webserver之后,在浏览器里面访问http://localhost:9999/index.html打开刚才的页面,开始测试是否可以正常上传。

          进阶篇一:STS临时认证

          Bce-bos-uploader支持STS(Security Token Service)临时授权的方式。服务端生成一组具体特定操作权限、具有一定时效性的临时AK/SK,这组临时的AK/SK可以暴露给浏览器端直接使用。用户只需要将服务端返回的AK/SK及SessionToken设置为bce-bos-uploader对应的bos-ak、bos-sk和uptoken参数。 下图简单介绍了整个业务交互过程,关于STS方面的介绍请参考临时授权访问

          代码实现分为应用服务器端和客户端两部分,实现过程如下:

          1. 配置应用服务器端,以Nodejs实现为例,服务器端会返回AK/SK/SessionToken。
          2. 配置浏览器,根据服务器端返回的AK/SK/SessionToken初始化bce-bos-uploader参数。

          应用服务器端Nodejs实现

          var http = require('http');
          var url = require('url');
          var util = require('util');
          
          var STS = require('@baiducloud/sdk').STS;
          
          var kCredentials = {
              ak: '您的AK',
              sk: '您的SK'
          };
          
          function buildStsResponse() {
              var stsClient = new STS({
                  credentials: kCredentials,
                  region: 'bj'
              });
              return stsClient.getSessionToken(60 * 60 * 24, {
                  accessControlList: [{
                      service: 'bce:bos',
                      resource: ['bce-javascript-sdk-demo-test'],
                      region: '*',
                      effect: 'Allow',
                      permission: ['READ', 'WRITE']
                  }]
              }).then(function (response) {
                  var body = response.body;
                  return {
                      AccessKeyId: body.accessKeyId,
                      SecretAccessKey: body.secretAccessKey,
                      SessionToken: body.sessionToken,
                      Expiration: body.expiration
                  };
              });
          }
          
          http.createServer(function (req, res) {
              console.log(req.url);
          
              var query = url.parse(req.url, true).query;
          
              var promise = null;
          
              if (query.sts) {
                  promise = buildStsResponse();
              }
          
              promise.then(function (payload) {
                  res.writeHead(200, {
                      'Content-Type': 'text/javascript; charset=utf-8',
                      'Access-Control-Allow-Origin': '*'
                  });
          
                  if (query.callback) {
                      res.end(util.format('%s(%s)', query.callback, JSON.stringify(payload)));
                  }
                  else {
                      res.end(JSON.stringify(payload));
                  }
              });
          }).listen(1337);
          console.log('Server running at http://0.0.0.0:1337/');

          在服务器端,用与创建bosClient实例类似的方式创建一个stsClient实例。对于stsClient实例,主要有一个方法,那就是getSessionToken。这个方法接收两个参数,第一个参数是临时授权的有效期,以秒为单位;第二个单位是具体的权限控制,参见STS服务接口

          这个方法会异步访问STS授权服务器,返回一个promise对象。STS授权服务器会返回类似如下内容:

          {   
              body: {         
                  "accessKeyId": "d87a16e5ce1d47c1917b38ed03fbb329", 
                  "secretAccessKey": "e9b6f59ce06c45cdaaea2296111dab46",
                   "sessionToken": "MjUzZjQzNTY4OTE0NDRkNjg3N2E4YzJhZTc4YmU5ZDh8AAAAABwCAAB/HfHDVV2bu5xUf6rApt2YdSLG6+21UTC62EHvIuiaamtuMQQKNkR9PU2NJGVbuWgBn8Ot0atk0HnWYQGgwgyew24HtbrX3GFiR/cDymCowm0TI6OGq7k8pGuBiCczT8qZcarH7VdZBd1lkpYaXbtP7wQJqiochDXrswrCd+J/I2CeSQT6mJiMmvupUV06R89dWBL/Vcu7JQpdYBk0d5cp2B+gdaHddBobevlBmKQw50/oOykJIuho4Wn7FgOGPMPdod0Pf0s7lW/HgSnPOjZCgRl0pihs197rP3GWpnlJRyfdCY0g0GFG6T0/FsqDbxbi8lWzF1QRTmJzzh2Tax8xoPFKGMbpntp//vGP7oPYK1JoES34TjcdcZnLzIRnVIGaZAzmZMUhPEXE5RVX1w8jPEXMJJHSrFs3lJe13o9Dwg==",         
                  "createTime": "2016-02-16T14:01:29Z",         
                  "expiration": "2016-02-16T15:41:29Z",         
                  "userId": "5e433c4a8fe74765a7ec6fc147e25c80"     
              } 
          }

          服务器端需要把accessKeyId、secretAccessKey、sessionToken三个字段下发给浏览器端。

          配置浏览器端bce-bos-uploader参数

          使用STS临时授权机制时,只需要在各个服务初始化的时候把上面所说的参数accessKeyId、secretAccessKey、sessionToken引入就可以了。

          <!doctype html>
          <html>
            <head>
              <meta charset="utf-8" />
              <title>bce-bos-uploader simple demo</title>
              <!--[if lt IE 8]><script src="http://websdk.cdn.bcebos.com/bos/json3/lib/json3.min.js"></script><![endif]-->
              <!--[if lt IE 9]><script src="http://websdk.cdn.bcebos.com/bos/js-polyfills/es5.js"></script><![endif]-->
              <!--[if lt IE 10]><script src="http://websdk.cdn.bcebos.com/bos/moxie/bin/js/moxie.js"></script><![endif]-->
              <!-- BOS不提供jquery.min.js,开发者可以自行引入在线资源或本地资源 -->
              <script src="./node_modules/jquery/dist/jquery.min.js"></script>
              <!-- 引入bce-bos-uploader.bundle.js,建议通过npm安装成功后,引入本地资源 -->
              <script src="./node_modules/@baiducloud/bos-uploader/dist/bce-bos-uploader.bundle.js"></script>
            </head>
            <body>
            
              <input type="file" id="file" >
              <script>
              var uploader = new baidubce.bos.Uploader({
                browse_button: '#file',
                bos_bucket: '<your bucket>',
                bos_endpoint: 'http://bj.bcebos.com',
                bos_ak: '<your ak>', 
                bos_sk: '<your sk>',
                uptoken: '<your sessionToken>'
              });
              </script>
            </body>
          </html>

          进阶篇二:通过PostObject接口处理IE低版本

          因为IE低版本(IE8,IE9)对html5支持的不完善,为了在这些浏览器里面实现文件直传的功能, BOS开发了PostObject接口,通过一个multipart/form-data的格式,就可以把文件上传到BOS服务器。Postobject接口的签名模式下应用服务器端对policy生成签名,再返回给客户端。

          bce-bos-uploader已经实现了对这个接口的支持,使用之前需要进行额外的配置工作:

          配置应用服务器端

          1. 上传crossdomain.xml

          基于html5的跨域方案,我们需要设置跨域范文(CORS);如果通过flash来完成跨域数据交互的话,需要设置crossdomain.xml,可以直接把如下内容保存为crossdomain.xml,然后上传到Bucket的根目录。

          <?xml version="1.0" encoding="UTF-8" ?>
          <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
          <cross-domain-policy>
            <site-control permitted-cross-domain-policies="all"/>
            <allow-access-from domain="*" secure="false" />
            <allow-http-request-headers-from domain="*" headers="*" secure="false"/>
          </cross-domain-policy>

          如果bucket是private的,需要设置bucket为自定义权限来允许crossdomain.xml为public-read。在Console控制台选择bucket并进入“基础设置”页签,选择“Bucket权限设置”对应的“修改配置”,修改bucket的权限设置为“自定义权限”并添加权限。

          image.png

          1. 服务器端返回uptoken_url参数。

          使用PostObject处理IE低版本时,需要在bce-bos-uploader中配置uptoken_url参数。

          配置浏览器端bce-bos-uploader参数

          • 以html方式初始化bce-bos-uploader中的uptoken_url参数。

            bce-bos-uploader simple demo
          • 以js方式初始化bce-bos-uploader中的uptoken_url参数。

          原理篇一:在浏览器中直接上传文件到BOS

          如您不使用bce-bos-uploader,可以参考下面的内容完成如何在浏览器中直接上传文件到BOS。使用流程:

          1. 开启Bucket的跨域访问设置
          2. 查询ak/sk
          3. 初始化BosClient
          4. 处理上传逻辑

          开启Bucket的跨域访问

          受浏览器安全限制,如果想直接在浏览器中访问BOS服务,必须正确设置好相关bucket的跨域功能。设置方法请参考开启Bucket的跨域访问

          初始化设置

          var bosConfig = {
              credentials: {
                   ak: '从百度智能云控制台查询您的ak',
                   sk: '从百度智能云控制台查询上面这个ak所对应的sk'
               },
               endpoint: 'http://bj.bcebos.com' // 根据您选用bos服务的区域配置相应的endpoint
           };
           var bucket = 'bce-javascript-sdk-demo-test'; // 设置您想要操作的bucket
           var client = new baidubce.sdk.BosClient(bosConfig);

          后续我们可以使用client这个实例来进行BOS相关操作。

          上传逻辑

          我们可以通过调用client.putObjectFromBlob(bucket, key, blob, options)来完成文件的上传操作。

          这个函数支持4个参数,其中options是可选的。如果需要手工设置文件的的Content-Type,可以放到options参数里面。 如果没有手工设置,默认的Content-Type是application/oceat-stream。 另外,可以通过调用baidubce.sdk.MimeType.guess(ext)来根据后缀名得到一些常用的Content-Type。

          注意:因为Firefox兼容性的一个问题,如果上传的文件是 text/* 类型,Firefox 会自动添加 charset=utf-8 因此我们给 options 设置 Content-Type 的时候,需要手工加上 charset=utf-8,否则会导致浏览器计算的签名跟服务器计算的签名不一致,导致上传失败。

          // 监听文件上传的事件,假设页面中有:<input type="file" id="upload" /> $('#upload').on('change', function (evt) {
               var file = evt.target.files[0]; // 获取要上传的文件
               var key = file.name; // 保存到bos时的key,您可更改,默认以文件名作为key
               var blob = file;
          
               var ext = key.split(/\./g).pop();
               var mimeType = baidubce.sdk.MimeType.guess(ext);
               if (/^text\//.test(mimeType)) {
                   mimeType += '; charset=UTF-8';
               }
               var options = {
                   'Content-Type': mimeType
               };
          
                client.putObjectFromBlob(bucket, key, blob, options)
                   .then(function (res) {
                       // 上传完成,添加您的代码
                       console.log('上传成功');
                   })
                   .catch(function (err) {
                       // 上传失败,添加您的代码
                       console.error(err);
                   });
            });

          如果想获悉当前上传的进度,可以监听progress事件。

           client.on('progress', function (evt)
           {
               // 监听上传进度
               if (evt.lengthComputable)
           {
                   // 添加您的代码
                   var percentage = (evt.loaded / evt.total) * 100;
                   console.log('上传中,已上传了' + percentage + '%');
               }
           });

          原理篇二:大文件分块上传

          用户在使用浏览器上传文件到BOS的时候,如果遇到文件过大,需要先将文件分块然后再上传。上传过程中有可能会遇到页面关闭、浏览器崩溃、网络连接中断等问题,从而导致上传失败。BOS支持分块上传和断点续传功能。分块上传请参见“Object的分块上传”,下面介绍“断点续传”的实现方法。

          实现原理

          在我们使用文件分块上传(multipartUpload)的时候,BOS首先会为这个上传过程分配一个uploadId。然后我们将一个文件被分成了若干part,每个part独立上传,上传完成后,BOS 服务会为这个part生成一个eTag。当所有part都上传完成的时候,BOS 服务根据这些eTag和uploadId把正确的part找出来,并组合成原本的文件。

          在这个过程中,BOS 并不需要所有的part一下子全部上传完毕,而是可以分多次进行。这也就是就,上传过程中,当页面意外关闭时,我们可以不必从头开始重新上传,而只需要把未上传成功的part的再次上传就可以。当然,前提是我们需要把此次上传的uploadId和上传完成的part的etag保存下来(不过,更推荐的做法是通过listParts接口来查询更精确的已上传分块信息)。在上传一个part之前,可以先检查一下,这个part是否已经上传过了,如果以前已上传成功,那就直接跳过这个part的上传过程。

          对于uploadId的存储,需要满足不受页面关闭的影响,比较理想的做法是存储在localStorage中。

          本地存储

          在保存uploadId时,我们需要为它指定一个key,让不同的文件、不同的上传过程区分开。本示例采用文件名、文件大小、分区大小、bucket名称、object名称组合成这个key:

          var generateLocalKey = function (blob, chunkSize, bucket, object) {
               return [blob.name, blob.size, chunkSize, bucket, object].join('&');
           };

          注意:用这个方式生成的key并不准确,如果两次上传过程中,选择了两个文件名相同、文件大小相同,但内容不同的文件,那么用这样的方式并不能正确区分这两个文件。更严谨的方式是根据文件名和文件内容计算MD5,并以此为key。

          存储方式我们选择localStorage:

          var getUploadId = function (key) {
               return localStorage.getItem(key);
           };  
          var setUploadId = function (key, uploadId) {
               return localStorage.setItem(key, uploadId);
           };  
          var removeUploadId = function (key) {
               return localStorage.removeItem(key);
           };

          初始化分块上传

          在初始化分块上传时,有两种可能:

          • 如果已经存在此文件的uploadId,那么跳过initiateMultipartUpload()方法,改为调用listParts()来获取已上传分块信息;
          • 如果没有此文件的uploadId,那么调用initiateMultipartUpload()方法获得新的uploadId,并将这个uploadId保存在localStorage中。

            // ...省略BosClient初始化过程 // var bosClient = new BosClient(bosConfig);

            var initiateMultipartUpload = function (file, chunkSize, bucket, object) { // 根据文件生成localStorage的key var key = generateLocalKey(file, chunkSize, bucket, object); // 获取对应的uploadId var uploadId = getUploadId(key); if (uploadId) { // uploadId存在,说明有未完成的分块上传。 // 那么调用listParts()获取已上传分块信息。 return BosClient.listParts(bucket, object, uploadId) .then(function (response) { // response.body.parts里包含了已上传分块的信息 response.body.uploadId = uploadId; return response; }); } else { // uploadId不存在,那么用正常的流程初始化 return BosClient.initiateMultipartUpload(bucket, object) .then(function (response) { // response.body.uploadId为新生成的uploadId response.body.parts = []; // 为了下次能使用断点续传,我们需要把新生成的uploadId保存下来 setUploadId(key, response.body.uploadId); return response; }); } }

          分块上传

          在对大文件分割分块时,我们可以跟以上传的分块列表进行比较,以确定是否需要真的进行上传。

          function getEtag(partNumber, parts){
               // 从已上传part列表中找出特定partNumber的part的eTag
               for(var i = 0, l = parts.length; i < l; i++){
                   if (parts[i].partNumber === partNumber) {
                       return parts[i].eTag;
                   }
               }
               return null;
           }
            function getTasks (file, uploadId, chunkSize, bucket, object, parts) {
               var leftSize = file.size;
               var offset = 0;
               var partNumber = 1;
          
               var tasks = [];
          
               while (leftSize > 0) {
                   var partSize = Math.min(leftSize, chunkSize);
                   var task = {
                       file: file,
                       uploadId: uploadId,
                       bucket: bucket,
                       object: object,
                       partNumber: partNumber,
                       partSize: partSize,
                       start: offset,
                       stop: offset + partSize - 1
                   };
          
                    // 如果在已上传完成的分块列表中找到这个分块的etag,那么记录下来
                   var etag = getEtag(partNumber, parts);
                   if (etag){
                       task.etag = etag;
                   }
                    tasks.push(task);
                    leftSize -= partSize;
                   offset += partSize;
                   partNumber += 1;
               }
                return tasks;
           }

          在进行分块上传处理的时候,根据是否已带有etag字段来决定是否需要上传:

          function uploadPartFile(state, bosClient) {
               return function(task, callback) {
                   if (task.etag) {
                       // 如果有etag字段,则直接跳过上传
                       callback(null, {
                           http_headers: {
                               etag: task.etag
                           },
                           body: {}
                       });
                   }
                   else {
                       // 否则进行上传
                       var blob = task.file.slice(task.start, task.stop + 1);
                       bosClient.uploadPartFromBlob(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, blob)
                           .then(function(res) {
                               ++state.loaded;
                              callbacknull,res);
                           })
                           .catch(function(err) {
                               callback(err);
                           });
                   }
               };
           }

          流程代码

          我们对每个步骤的代码做了一些小修改,但整个流程的代码与分块上传很类似:

          var chunkSize = 5 * 1024 * 1024; // 分块大小
          var uploadId;
          initiateMultipartUpload(file, chunkSize, bucket, object)
               .then(function(response) {
                   uploadId = response.body.uploadId; // uploadId,可能是服务器刚刚生成的,也可能是从localStorage获取的
                   var parts = response.body.parts || []; // 已上传的分块列表。如果是新上传,则为空数组
                   var deferred = sdk.Q.defer();
                   var tasks = getTasks(blob, uploadId, chunkSize, bucket, key, parts);
                   var state = {
                       lengthComputable: true,
                       loaded: parts.length, // 已上传的分块数
                       total: tasks.length
                   }; 
                   // 如果已上传的分块数大于0,可以先修改一下文件上传进度
                   bosClient.emit('progress', state);
                   // 为了管理分块上传,使用了async(https://github.com/caolan/async)库来进行异步处理
                   var THREADS = 2; // 同时上传的分块数量
                   async.mapLimit(tasks, THREADS, uploadPartFile(state, bosClient), function(err, results) {
                       if (err) {
                           deferred.reject(err);
                       } else {
                           deferred.resolve(results);
                       }
                   });
                   return deferred.promise;
               })
               .then(function(allResponse) {
                   var partList = [];
                   allResponse.forEach(function(response, index) {
                       // 生成分块清单
                       partList.push({
                           partNumber: index + 1,
                           eTag: response.http_headers.etag
                       });
                   }); 
          
                   // 所有分块上传完成后,可以删除对应的`uploadId`了
                   removeUploadId(key, uploadId);
          
                   return bosClient.completeMultipartUpload(bucket, key, uploadId, partList); // 完成上传
               })
               .then(function (res) {
                   // 上传完成
               })
               .catch(function (err) {
                   // 上传失败,添加您的代码
                   console.error(err);
               });

          原理篇三:STS临时认证

          Bce-bos-uploader支持STS(Security Token Service)临时授权的方式。服务端生成一组具体特定操作权限、具有一定时效性的临时AK/SK,这组临时的AK/SK可以暴露给浏览器端直接使用。用户只需要将服务端返回的AK/SK及SessionToken设置为bce-bos-uploader对应的bos-ak、bos-sk和uptoken参数。

          关于STS方面的介绍请参考临时授权访问。使用流程:

          1. 配置应用服务器端Nodejs实现
          2. 获取临时AK/SK/SessionToken
          3. 初始化bce-bos-uploader参数

          应用服务器端Nodejs实现

          var http = require('http');
          var url = require('url');
          var util = require('util');
          
          var STS = require('@baiducloud/sdk').STS;
          
          var kCredentials = {
              ak: '您的AK',
              sk: '您的SK'
          };
          
          function buildStsResponse() {
              var stsClient = new STS({
                  credentials: kCredentials,
                  region: 'bj'
              });
              return stsClient.getSessionToken(60 * 60 * 24, {
                  accessControlList: [{
                      service: 'bce:bos',
                      resource: ['bce-javascript-sdk-demo-test'],
                      region: '*',
                      effect: 'Allow',
                      permission: ['READ', 'WRITE']
                  }]
              }).then(function (response) {
                  var body = response.body;
                  return {
                      AccessKeyId: body.accessKeyId,
                      SecretAccessKey: body.secretAccessKey,
                      SessionToken: body.sessionToken,
                      Expiration: body.expiration
                  };
              });
          }
          
          http.createServer(function (req, res) {
              console.log(req.url);
          
              var query = url.parse(req.url, true).query;
          
              var promise = null;
          
              if (query.sts) {
                  promise = buildStsResponse();
              }
          
              promise.then(function (payload) {
                  res.writeHead(200, {
                      'Content-Type': 'text/javascript; charset=utf-8',
                      'Access-Control-Allow-Origin': '*'
                  });
          
                  if (query.callback) {
                      res.end(util.format('%s(%s)', query.callback, JSON.stringify(payload)));
                  }
                  else {
                      res.end(JSON.stringify(payload));
                  }
              });
          }).listen(1337);
          console.log('Server running at http://0.0.0.0:1337/');

          在服务器端,用与创建bosClient实例类似的方式创建一个stsClient实例。对于stsClient实例,主要有一个方法,那就是getSessionToken。这个方法接收两个参数,第一个参数是临时授权的有效期,以秒为单位;第二个单位是具体的权限控制,参见STS服务接口

          这个方法会异步访问STS授权服务器,返回一个promise对象。STS授权服务器会返回类似如下内容:

          {   
              body: {         
                  "accessKeyId": "d87a16e5ce1d47c1917b38ed03fbb329",
                  "secretAccessKey": "e9b6f59ce06c45cdaaea2296111dab46", 
                  "sessionToken": "MjUzZjQzNTY4OTE0NDRkNjg3N2E4YzJhZTc4YmU5ZDh8AAAAABwCAAB/HfHDVV2bu5xUf6rApt2YdSLG6+21UTC62EHvIuiaamtuMQQKNkR9PU2NJGVbuWgBn8Ot0atk0HnWYQGgwgyew24HtbrX3GFiR/cDymCowm0TI6OGq7k8pGuBiCczT8qZcarH7VdZBd1lkpYaXbtP7wQJqiochDXrswrCd+J/I2CeSQT6mJiMmvupUV06R89dWBL/Vcu7JQpdYBk0d5cp2B+gdaHddBobevlBmKQw50/oOykJIuho4Wn7FgOGPMPdod0Pf0s7lW/HgSnPOjZCgRl0pihs197rP3GWpnlJRyfdCY0g0GFG6T0/FsqDbxbi8lWzF1QRTmJzzh2Tax8xoPFKGMbpntp//vGP7oPYK1JoES34TjcdcZnLzIRnVIGaZAzmZMUhPEXE5RVX1w8jPEXMJJHSrFs3lJe13o9Dwg==",         
                  "createTime": "2016-02-16T14:01:29Z",         
                  "expiration": "2016-02-16T15:41:29Z",         
                  "userId": "5e433c4a8fe74765a7ec6fc147e25c80"     
              } 
          }

          服务器端需要把accessKeyId、secretAccessKey、sessionToken三个字段下发给浏览器端。

          浏览器前端实现

          前端使用STS临时授权机制时,只需要在各个服务初始化的时候把上面所说的参数accessKeyId、secretAccessKey、sessionToken引入就可以了。以BOS为例:

          var bosConfig = {
               credentials: {
                   ak: '{accessKeyId}', // STS服务器下发的临时ak
                   sk: '{secretAccessKey}' // STS服务器下发的临时sk
               },
               sessionToken: '{sessionToken}',  // STS服务器下发的sessionToken
               endpoint: 'http://bj.bcebos.com'
           };
          var client = new baidubce.sdk.BosClient(bosConfig);