PHP分片上传

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

分片上传流程

分片上传(Multipart Upload)分为以下三个步骤:

  1. 初始化一个分片上传事件。

    调用$ossClient->initiateMultipartUpload方法返回OSS创建的全局唯一的uploadId。

  2. 上传分片。

    调用$ossClient->uploadPart方法上传分片数据。

    说明
    • 对于同一个uploadId,分片号(PartNumber)标识了该分片在整个文件内的相对位置。如果使用同一个分片号上传了新的数据,则OSS上该分片已有的数据将会被覆盖。

    • OSS将收到的分片数据的MD5值放在ETag头内返回给用户。

    • OSS计算上传数据的MD5值,并与SDK计算的MD5值比较,如果不一致则返回InvalidDigest错误码。

  3. 完成分片上传。

    调用$ossClient->completeMultipartUpload方法将所有分片合并成完整的文件。

分片上传完整示例

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

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;
use OSS\Core\OssUtil;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';
//填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。
$object = 'exampledir/exampleobject.txt';
// 填写本地文件的完整路径。
$uploadFile = 'D:\\localpath\\examplefile.txt';
$initOptions = array(
    OssClient::OSS_HEADERS  => array(
        // 指定该Object被下载时的网页缓存行为。
        // 'Cache-Control' => 'no-cache',
        // 指定该Object被下载时的名称。
        // 'Content-Disposition' => 'attachment;filename=oss_download.jpg',
        // 指定该Object被下载时的内容编码格式。
        // 'Content-Encoding' => 'utf-8',
        // 指定过期时间,单位为毫秒。
        // 'Expires' => 150,
        // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
        //'x-oss-forbid-overwrite' => 'true',
        // 指定上传该Object的每个part时使用的服务器端加密方式。
        // 'x-oss-server-side-encryption'=> 'KMS',
        // 指定Object的加密算法。
        // 'x-oss-server-side-data-encryption'=>'SM4',
        // 指定KMS托管的用户主密钥。
        //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****',
        // 指定Object的存储类型。
        // 'x-oss-storage-class' => 'Standard',
        // 指定Object的对象标签,可同时设置多个标签。
        // 'x-oss-tagging' => 'TagA=A&TagB=B',
    ),
);

/**
 *  步骤1:初始化一个分片上传事件,并获取uploadId。
 */
try{
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);
    //返回uploadId。uploadId是分片上传事件的唯一标识,您可以根据uploadId发起相关的操作,如取消分片上传、查询分片上传等。
    $uploadId = $ossClient->initiateMultipartUpload($bucket, $object, $initOptions);
    print("initiateMultipartUpload OK" . "\n");
    // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
    // 如果您需要根据您需要uploadId执行取消分片上传事件的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。 
    // 如果您需要根据您需要uploadId执行列举已上传分片的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
    //print("UploadId: " . $uploadId . "\n");
} catch(OssException $e) {
    printf($e->getMessage() . "\n");
    return;
}

/*
 * 步骤2:上传分片。
 */
$partSize = 10 * 1024 * 1024;
$uploadFileSize = sprintf('%u',filesize($uploadFile));
$pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize);
$responseUploadPart = array();
$uploadPosition = 0;
$isCheckMd5 = true;
foreach ($pieces as $i => $piece) {
    $fromPos = $uploadPosition + (integer)$piece[$ossClient::OSS_SEEK_TO];
    $toPos = (integer)$piece[$ossClient::OSS_LENGTH] + $fromPos - 1;
    $upOptions = array(
        // 上传文件。
        $ossClient::OSS_FILE_UPLOAD => $uploadFile,
        // 设置分片号。
        $ossClient::OSS_PART_NUM => ($i + 1),
        // 指定分片上传起始位置。
        $ossClient::OSS_SEEK_TO => $fromPos,
        // 指定文件长度。
        $ossClient::OSS_LENGTH => $toPos - $fromPos + 1,
        // 是否开启MD5校验,true为开启。
        $ossClient::OSS_CHECK_MD5 => $isCheckMd5,
    );
    // 开启MD5校验。
    if ($isCheckMd5) {
        $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
        $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5;
    }
    try {
        // 上传分片。
        $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions);
        printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n");
    } catch(OssException $e) {
        printf("initiateMultipartUpload, uploadPart - part#{$i} FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }

}
// $uploadParts是由每个分片的ETag和分片号(PartNumber)组成的数组。
$uploadParts = array();
foreach ($responseUploadPart as $i => $eTag) {
    $uploadParts[] = array(
        'PartNumber' => ($i + 1),
        'ETag' => $eTag,
    );
}
/**
 * 步骤3:完成上传。
 */
$comOptions['headers'] = array(
    // 指定完成分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
    // 'x-oss-forbid-overwrite' => 'true',
    // 如果指定了x-oss-complete-all:yes,则OSS会列举当前uploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
    // 'x-oss-complete-all'=> 'yes'
);

try {
    // 执行completeMultipartUpload操作时,需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
    $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts,$comOptions);
    printf( "Complete Multipart Upload OK\n");
}  catch(OssException $e) {
    printf("Complete Multipart Upload FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
           

分片上传本地文件

以下代码用于分片上传本地文件到OSS。

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';
//填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。
$object = 'exampledir/exampleobject.txt';
// 填写本地文件的完整路径。
$file = 'D:\\localpath\\examplefile.txt';

$options = array(
    OssClient::OSS_CHECK_MD5 => true,
    OssClient::OSS_PART_SIZE => 1,
);
try{
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);

    $ossClient->multiuploadFile($bucket, $object, $file, $options);
} catch(OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ":  OK" . "\n");            

分片上传目录

以下代码用于分片上传本地目录(包含此目录下的所有文件)到OSS。

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';
// 填写待上传的本地目录的完整路径。
$localDirectory = "D:\\localpath";
// 填写上传至OSS的目录前缀。
$prefix = "samples/codes/";
try {
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);

    $ossClient->uploadDir($bucket, $prefix, $localDirectory);
}  catch(OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ":  OK" . "\n");            

取消分片上传事件

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

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';
//填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。
$object = 'exampledir/exampleobject.txt';
// 填写uploadId,例如0004B999EF518A1FE585B0C9360D****。uploadId来源于调用InitiateMultipartUpload完成初始化分片之后的返回结果。
$upload_id = '0004B999EF518A1FE585B0C9360D****';

try{
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);

    $ossClient->abortMultipartUpload($bucket, $object, $upload_id);
} catch(OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ": OK" . "\n");            

列举已上传的分片

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

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';
//填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。
$object = 'exampledir/exampleobject.txt';
// 填写uploadId,例如0004B999EF518A1FE585B0C9360D****。uploadId来源于调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前的返回结果。
$upload_id = '0004B999EF518A1FE585B0C9360D****';

try{
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);

    $listPartsInfo = $ossClient->listParts($bucket, $object, $uploadId);
    foreach ($listPartsInfo->getListPart() as $partInfo) {
        print($partInfo->getPartNumber() . "\t" . $partInfo->getSize() . "\t" . $partInfo->getETag() . "\t" . $partInfo->getLastModified() . "\n");
    }
} catch(OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ": OK" . "\n");            

列举分片上传事件

调用listMultipartUploads方法列举出所有执行中的分片上传事件,即已初始化但还未完成(Complete)或者还未中止(Abort)的分片上传事件。

<?php
if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use OSS\CoreOssException;

// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 
$provider = new EnvironmentVariableCredentialsProvider();
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
$endpoint = 'https://oss-cn-hangzhou.aliyuncs.com';
// 填写Bucket名称,例如examplebucket。
$bucket= 'examplebucket';

$options = array(
    'delimiter' => '/',
    'max-uploads' => 100,
    'key-marker' => '',
    'prefix' => '',
    'upload-id-marker' => ''
);
try {
    $config = array(
        "provider" => $provider,
        "endpoint" => $endpoint,
        "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
        "region"=> "cn-hangzhou"
    );
    $ossClient = new OssClient($config);

    $listMultipartUploadInfo = $ossClient->listMultipartUploads($bucket, $options);
} catch(OssException $e) {
    printf(__FUNCTION__ . ": listMultipartUploads FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
printf(__FUNCTION__ . ": listMultipartUploads OK\n");
$listUploadInfo = $listMultipartUploadInfo->getUploads();
var_dump($listUploadInfo);            

$options的参数说明请参见下表。

参数

说明

delimiter

用于对文件名称进行分组的一个字符。所有文件名称包含指定的前缀且第一次出现delimiter字符之间的文件作为一组元素。

key-marker

所有文件名称的字典序大于key-marker参数值的分片上传事件。与upload-id-marker参数一同使用,用于指定返回结果的起始位置。

max-uploads

限定此次返回分片上传事件的最大个数,默认值和最大值均为1000。

prefix

限定返回的文件名称必须以指定的prefix作为前缀。

说明

使用prefix查询时,返回的文件名称中仍会包含prefix。

upload-id-marker

与key-marker参数一同使用,用于指定返回结果的起始位置。 如果未设置key-marker参数,则此参数无效。如果设置了key-marker参数,则查询结果中包含:

  • 文件名称的字典序大于key-marker参数值的所有文件。

  • 文件名称等于key-marker参数值且uploadId比upload-id-marker参数值大的所有分片上传事件。

相关文档

  • 关于分片上传的完整示例代码,请参见GitHub示例

  • 分片上传的完整实现涉及三个API接口,详情如下:

  • 关于取消分片上传事件的API接口说明,请参见AbortMultipartUpload

  • 关于列举已上传分片的API接口说明,请参见ListParts

  • 关于列举所有执行中的分片上传事件(即已初始化但尚未完成或已取消的分片上传事件)的API接口说明,请参见ListMultipartUploads