OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个数据块(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。

背景信息

当需要上传的文件较大时,您可以通过MultipartUpload接口进行分片上传。分片上传是指将要上传的文件分成多个数据块(Part)来分别上传。当其中一些分片上传失败后,OSS将保留上传进度记录,再次重传时只需要上传失败的分片,而不需要重新上传整个文件。一般大于100 MB的文件,建议采用分片上传的方法,通过断点续传和重试,提高上传成功率。

在使用MultipartUpload接口时,如果遇到ConnectionTimeoutError超时问题,业务方需自行处理超时逻辑。例如通过缩小分片大小、增加超时时间、重试请求或者捕获ConnectionTimeoutError错误等方法处理超时。更多信息,请参见网络错误处理

分片上传涉及的相关参数说明请参见下表。

类型 参数 说明
必选参数 name {String} Object的名称。即不包括Bucket名称在内的Object的完整路径。
file {String|File} 表示文件路径或者HTML5文件。
[options] {Object} 可选参数 [checkpoint] {Object} 记录本地分片上传结果的文件。开启断点续传功能时需要设置此参数,上传过程中的进度信息会保存在该文件中,如果某一分片上传失败,再次上传时会根据文件中记录的点继续上传。上传完成后,该文件会被删除。
[parallel] {Number} 并发上传的分片个数,默认为5。无特殊需求,不需要手动设置此参数。
[partSize] {Number} 指定上传的每个分片的大小,范围为100 KB~5 GB。单个分片默认大小为1 * 1024 * 1024(即1 MB)。无特殊需求,不需要手动设置此参数。
[progress] {Function} 表示进度回调函数,用于获取上传进度,可以是async的函数形式。回调函数包含三个参数:
  • percentage {Number}:进度百分比(0~1之间的小数)。
  • checkpoint {Object}:与[options] {Object}中的[checkpoint] {Object}定义相同。
  • res {Object}:单个分片上传成功返回的response。
[meta] {Object} 用户自定义的Header meta信息,Header前缀为x-oss-meta-
[mime] {String} 设置Content-Type请求头。
[headers] {Object} 其他Header。更多信息,请参见RFC 2616。其中:
  • 'Cache-Control':通用消息头被用在HTTP请求和响应中通过指定指令来实现缓存机制,例如Cache-Control: public, no-cache
  • 'Content-Disposition':指示回复的内容该以何种形式展示,是以网页预览的形式,还是以附件的形式下载并保存到本地,例如Content-Disposition: somename
  • 'Content-Encoding':用于对特定媒体类型的数据进行压缩,例如Content-Encoding: gzip
  • 'Expires':过期时间,单位为毫秒。

以下示例代码中的catch语法,请自行学习es6 promise、async/await。 SDK的使用方式,请参见安装

分片上传完整示例

以下通过一个完整的示例对分片上传的流程进行逐步解析:

const OSS = require('ali-oss');

const client = new OSS({
  // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
  region: '<Your region>',
  // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: '<Your bucket name>',
});

const progress = (p, _checkpoint) => {
  console.log(p); // Object的上传进度。
  console.log(_checkpoint); // 分片上传的断点信息。
};

// 开始分片上传。
async function multipartUpload() {
  try {
    // object-name可以定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至Bucket根目录或Bucket下的指定目录。
    const result = await client.multipartUpload('<Object Name>', 'file-object', {
      progress,
      // meta是用户自定义的元数据,通过head接口可以获取到Object的meta数据。
      meta: {
        year: 2020,
        people: 'test',
      },
    });
    console.log(result);
    const head = await client.head('object-name');
    console.log(head);
  } catch (e) {
    // 捕获超时异常。
    if (e.code === 'ConnectionTimeoutError') {
      console.log('TimeoutError');
      // do ConnectionTimeoutError operation
    }
    console.log(e);
  }
}

multipartUpload();
注意 Node.js分片上传过程中不支持MD5校验。建议分片上传完成后调用CRC64库自行判断是否进行CRC64校验。

取消分片上传事件

您可以调用client.abortMultipartUpload方法来取消分片上传事件。当一个分片上传事件被取消后,无法再使用这个uploadId做任何操作,已经上传的分片数据会被删除。

以下代码用于取消分片上传事件。

const OSS = require('ali-oss');

const client = new OSS({
  // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
  region: '<Your region>',
  // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: '<Your bucket name>',
});

async function abortMultipartUpload() {
  const name = '<Object Name>'; // Object所在Bucket的完整路径。
  const uploadId = '<Upload Id>'; // 分片上传uploadId。
  const result = await client.abortMultipartUpload(name, uploadId);
  console.log(result);
}

abortMultipartUpload();

列举分片上传事件

调用client.listUploads方法列举出分片上传事件示例代码如下:

const OSS = require('ali-oss');

const client = new OSS({
  // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
  region: '<Your region>',
  // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: '<Your bucket name>',
});

async function listUploads(query = {}) {
  // query参数支持prefix、marker、delimiter、upload-id-marker和max-uploads。
  const result = await client.listUploads(query);

  result.uploads.forEach(upload => {
    console.log(upload.uploadId); // 分片上传的uploadId。
    console.log(upload.name); // 将所有上传完成后的分片(Part)组合为一个完整的Object,并指定Object所在Bucket的完整路径。
  });
}

const query = {
  'max-uploads': 1000, // 限定此次返回Multipart Uploads事件的最大数目。max-uploads默认值和最大值均为1000。
};
listUploads(query);

列举已上传的分片

调用client.listParts方法列举指定uploadId下所有已经上传成功的分片。

  • 简单列举已上传的分片

    以下代码用于简单列举已上传的分片:

    const OSS = require('ali-oss');
    
    const client = new OSS({
      // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
      region: '<Your region>',
      // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
      accessKeyId: '<Your AccessKeyId>',
      accessKeySecret: '<Your AccessKeySecret>',
      bucket: '<Your bucket name>',
    });
    
    async function listParts() {
      const query = {
        'max-parts': 1000, // 指定OSS响应中的最大分片(Part)数目。max-parts默认值和最大值均为1000。
      };
      const result = await client.listParts('<Object Name>', '<Upload Id>', query);
    
      result.parts.forEach(part => {
        console.log(part.PartNumber);
        console.log(part.LastModified);
        console.log(part.ETag);
        console.log(part.Size);
      });
    }
    
    listParts();
  • 列举所有已上传的分片

    默认情况下,client.listParts只能一次列举1000个分片。当分片数量大于1000时,请使用以下代码列举所有已上传的分片。

    const OSS = require('ali-oss')
    
    const client = new OSS({
      // region以杭州为例(oss-cn-hangzhou),其他region按实际情况填写。
      region: '<Your region>',
      // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
      accessKeyId: '<Your AccessKeyId>',
      accessKeySecret: '<Your AccessKeySecret>',
      bucket: '<Your bucket name>',
    });
    
    async function listParts() {
      const query = {
        'max-parts': 1000, // 设置OSS响应中的最大分片(Part)数目。max-parts默认值和最大值均为1000。
      };
      let result;
      do {
        result = await client.listParts('<Object Name>', '<Upload Id>', query);
        // 设置列举分片的起始位置。
        query['part-number-marker'] = result.nextPartNumberMarker;
        result.parts.forEach(part => {
          console.log(part.PartNumber);
          console.log(part.LastModified);
          console.log(part.ETag);
          console.log(part.Size);
        });
      } while (result.isTruncated === "true");
    }
    
    listParts();