5. 文件上传

在NOS中用户的基本操作单元是对象,亦可以理解为文件,NOS PHP SDK提供了丰富的上传接口,可以通过以下的方式上传文件:

  • 字符串上传
  • 本地文件上传
  • 分片上传

字符串上传、本地文件上传最大为100M,分片上传对文件大小没有限制

5.1. 字符串上传

您可以使用NosClient::putObject上传字符串内容到文件中,具体实现如下:

<?php
/**
 * 上传字符串作为object的内容
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function putObject($nosClient, $bucket)
{
    $object = "nos-php-sdk-test/upload-test-object-name.txt";
    $content = file_get_contents(__FILE__);
    try{
        $nosClient->putObject($bucket, $object, $content);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": OK" . "\n");
}

Attention

  1. 上传的字符串内容不超过100M

5.2. 本地文件上传

您可以使用NosClient::uploadFile上传文件内容,具体实现如下:

<?php
/**
 * 上传指定的本地文件内容
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function uploadFile($nosClient, $bucket)
{
    $object = "nos-php-sdk-test/upload-test-object-name.txt";
    $filePath = __FILE__;
    try{
        $nosClient->uploadFile($bucket, $object, $filePath);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": OK" . "\n");
}

Attention

  1. 上传的文件内容不超过100M

5.3. 分片上传

除了通过putObject接口上传文件到NOS之外,NOS还提供了另外一种上传模式-分片上传,用户可以在如下应用场景内(但不限于此),使用分片上传模式,如:

  • 需支持断点上传
  • 上传超过100M的文件
  • 网络条件较差,经常和NOS服务器断开连接
  • 上传文件之前无法确定文件的大小

5.3.1. 分片上传本地文件

您可以使用NosClient::multiuploadFile来上传一个文件,该方法封装了细节,会根据传入的文件及分片的大小自动选择上传方式,对于分片大小小于文件大下的情况下会使用分片上传:

<?php
/**
 * 通过multipart上传文件
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function multiuploadFile($nosClient, $bucket)
{
    $object = "test/multipart-test.txt";
    $file = __FILE__;
    try{
        $nosClient->multiuploadFile($bucket, $object, $file);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ":  OK" . "\n");
}

Attention

  1. 默认的分片大小为5M

5.3.2. 原始接口分片上传

您可以使用原始的分片上传接口进行分片上传,一般流程如下所示:

  • 初始化一个分片上传任务(InitiateMultipartUpload)
  • 逐个或并行上传分片(UploadPart)
  • 完成分片上传(CompleteMultipartUpload)或者取消分片上传(AbortMultipartUpload)

下面通过一个完整的示例说明了如何通过原始的api接口一步一步的进行分片上传操作,如果用户需要做断点续传等高级操作,可以参考下面代码:

<?php
/**
 * 使用基本的api分阶段进行分片上传
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @throws NosException
 */
function putObjectByRawApis($nosClient, $bucket)
{
    $object = "test/multipart-test.txt";
    /**
     *  step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id
     */
    try{
        $uploadId = $nosClient->initiateMultipartUpload($bucket, $object);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": initiateMultipartUpload FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": initiateMultipartUpload OK" . "\n");
    /*
     * step 2. 上传分片
     */
    // 分片大小10M
    $partSize = 10 * 1024 * 1024;
    $uploadFile = __FILE__;
    $uploadFileSize = filesize($uploadFile);
    $pieces = $nosClient->generateMultiuploadParts($uploadFileSize, $partSize);
    $responseUploadPart = array();
    $uploadPosition = 0;
    $isCheckMd5 = true;
    foreach ($pieces as $i => $piece) {
        $fromPos = $uploadPosition + (integer)$piece[NosClient::NOS_SEEK_TO];
        $toPos = (integer)$piece[NosClient::NOS_LENGTH] + $fromPos - 1;
        $upOptions = array(
            $nosClient::NOS_FILE_UPLOAD => $uploadFile,
            $nosClient::NOS_PART_NUM => ($i + 1),
            $nosClient::NOS_SEEK_TO => $fromPos,
            $nosClient::NOS_LENGTH => $toPos - $fromPos + 1,
            $nosClient::NOS_CHECK_MD5 => $isCheckMd5,
        );
        if ($isCheckMd5) {
            $contentMd5 = NosUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
            $upOptions[$nosClient::NOS_CONTENT_MD5] = $contentMd5;
        }
        //2. 将每一分片上传到NOS
        try {
            $responseUploadPart[] = $nosClient->uploadPart($bucket, $object, $uploadId, $upOptions);
        } catch(NosException $e) {
            printf(__FUNCTION__ . ": initiateMultipartUpload, uploadPart - part#{$i} FAILED\n");
            printf($e->getMessage() . "\n");
            return;
        }
        printf(__FUNCTION__ . ": initiateMultipartUpload, uploadPart - part#{$i} OK\n");
    }
    $uploadParts = array();
    foreach ($responseUploadPart as $i => $eTag) {
        $uploadParts[] = array(
            'PartNumber' => ($i + 1),
            'ETag' => $eTag,
        );
    }
    /**
     * step 3. 完成上传
     */
    try {
        $nosClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts);
    }  catch(NosException $e) {
        printf(__FUNCTION__ . ": completeMultipartUpload FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    printf(__FUNCTION__ . ": completeMultipartUpload OK\n");
}

Attention

  1. 上面程序一共分为三个步骤:1. initiate 2. uploadPart 3. complete
  2. UploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于或等于16K。但是Upload Part接口并不会立即校验上传Part的大小(因为不知道是否为最后一块);只有当Complete Multipart Upload的时候才会校验。
  3. Part号码的范围是1~10000。如果超出这个范围,NOS将返回InvalidArgument的错误码。
  4. Part的大小为16K到100M
  5. 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
  6. 分片上传任务初始化或上传部分分片后,可以使用abortMultipartUpload接口中止分片上传事件。当分片上传事件被中止后,就不能再使用这个Upload ID做任何操作,已经上传的分片数据也会被删除。
  7. 每次上传Part之后,NOS的返回结果会包含一个 PartETag 对象,它是上传块的ETag与块编号(PartNumber)的组合。在后续完成分片上传的步骤中会用到它,因此我们需要将其保存起来,然后在第三步complete的时候使用,具体操作参考上面代码。

5.3.3. 查看已经上传的分片

查看已经上传的分片可以罗列出指定Upload ID(InitiateMultipartUpload时获取)所属的所有已经上传成功的分片,您可以通过NosClient::listParts接口获取已经上传的分片,可以参考以下代码:

   /**
    * 查看已上传的分片
    *
    * @param NosClient $nosClient NosClient实例
    * @param string $bucket 存储空间名称
    * @param string $uploadId  upload Id
    * @return null
    */
   function listParts($nosClient, $bucket, $uploadId)
   {
       $object = "nos-php-sdk-test/upload-test-object-name.txt";
       try{
           $options = array();
           $options['max-parts'] = 100;
           $optiosn['part-number-marker'] = '10';
           $listPartsInfo = $nosClient->listParts($bucket, $object, $upload_id,$options);
           foreach ($listPartsInfo->getListPart() as $partInfo) {
               print($partInfo->getPartNumber() . "\t" . $partInfo->getSize() . "\t" . $partInfo->getETag() . "\t" . $partInfo->getLastModified() . "\n");
           }
       } catch(NosException $e) {
           printf(__FUNCTION__ . ": FAILED\n");
           printf($e->getMessage() . "\n");
           return;
       }
       print(__FUNCTION__ . ": OK" . "\n");
   }

查看已经上传的分片可以指定以下参数:
参数 描述
max-parts 响应中的limit个数 类型:整型
part-number-marker 分块号的界限,只有更大的分块号会被列出来。 类型:字符串

5.3.4. 查看当前正在进行的分片上传任务

查看正在进行的分片上传任务可以罗列出正在进行,还未完成的分片上传任务,您可以通过NosClient::listMultipartUploads接口当前的上传任务,可以参考以下代码:

<?php
/**
 * 获取当前未完成的分片上传列表
 *
 * @param $nosClient NosClient
 * @param $bucket   string
 */
function listMultipartUploads($nosClient, $bucket) {
    $options = array(
                'max-uploads' => 100,
                'key-marker' => ''
            );
    try {
        $listMultipartUploadInfo = $nosClient->listMultipartUploads($bucket, $options);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": listMultipartUploads FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    printf(__FUNCTION__ . ": listMultipartUploads OK\n");
    $listUploadInfo = $listMultipartUploadInfo->getUploads();
    var_dump($listUploadInfo);
}

上述代码中使用的options参数如下:

参数值 描述
key-marker 指定某一uploads key,只有大于该key-marker的才会被列出
max-uploads 最多返回max-uploads条记录,默认1000

5.4. 设置文件元信息

文件元数据(object meta),是上传到NOS的文件属性描述信息:分为http标准属性和用户自定义元数据。文件元信息可以在各种上传方式(字符串上传、文件上传、分片上传)或copy文件时进行设置,元数据大小写不敏感。

5.4.1. 设定http header

NOS允许用户自定义http Header。http header相关信息请参考 RFC2616,几个常用的header说明如下:

名称 描述 默认值
Content-MD5 文件数据校验,设置了该值后NOS会启用文件内容MD5校验,把您提供的MD5与文件的MD5比较,不一致会抛出错误
Content-Type 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,如果没有扩展名则填默认值 application/octet-stream
Content-Disposition 指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称
Content-Length 上传的文件的长度,超过流/文件的长度会截断,不足为实际值 流/文件实际长度
Expires 缓存过期时间,NOS未使用,格式是格林威治时间(GMT)
Cache-Control 指定该Object被下载时的网页的缓存行为

下面的源代码实现了上传文件时设置Http header:

/**
 * 上传时设置文件的元数据
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function putObject($nosClient, $bucket)
{
    $object = "nos-php-sdk-test/upload-test-object-name.txt";
    $content = file_get_contents(__FILE__);
    $options = array(
        NosClient::NOS_HEADERS => array(
            'Expires' => '2016-12-12 08:00:00',
            'Content-Disposition' => 'attachment; filename="xxxxxx"',
    ));
    try{
        $nosClient->putObject($bucket, $object, $content, $options);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": OK" . "\n");
}

Attention

  1. 各种上传方式(包括字符串上传、文件上传、分片上传)都可以设置元数据信息,设置的方式相同
  2. 通过设置文件的Content-Type,可以修改文件的类型。
  3. 通过设置文件的Content-Disposition,可以控制文件的下载行为。

5.4.2. 用户自定义元数据

NOS支持用户自定义对象元数据,上传时可以指定对象自定义元数据,数据放在http头中传输,以x-nos-meta-开头,以下源代码实现对象自定义元数据上传:

<?php
/**
 * 上传时设置文件的自定义元数据
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function putObject($nosClient, $bucket)
{
    $object = "nos-php-sdk-test/upload-test-object-name.txt";
    $content = file_get_contents(__FILE__);
    $options = array(NosClient::NOS_HEADERS => array(
        'x-nos-meta-self-define-title' => 'user define meta info',
    ));
    try{
        $nosClient->putObject($bucket, $object, $content, $options);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": OK" . "\n");
}

Note

  1. 文件的元信息可以通过NosClient::headObjectInfo获取;
  2. user meta的名称大小写不敏感,例如设置为:’x-nos-meta-name’,读取名字为x-nos-meta-name的参数即可;

5.5. 上传时校验MD5

为了确保PHP SDK发送的数NOS服务端接收到的数据一致,NOS支持MD5校验。文件上传时(字符串上传、文件上传、分片上传)默认关闭MD5,如果您需要打开MD5校验,请上传文件的options设置。

下面的代码实现了上传时开启MD5校验:

<?php
/**
 * 上传时开启MD5校验
 *
 * @param NosClient $nosClient NosClient实例
 * @param string $bucket 存储空间名称
 * @return null
 */
function putObject($nosClient, $bucket)
{
    $object = "nos-php-sdk-test/upload-test-object-name.txt";
    $options = array(NosClient::NOS_CHECK_MD5 => true);
    try{
        $nosClient->uploadFile($bucket, $object, __FILE__, $options);
    } catch(NosException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }
    print(__FUNCTION__ . ": OK" . "\n");
}

Attention

  1. 校验MD5会有一定的性能损失;