# 分段上传
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/notice_3.0-zh-cn.png)
开发过程中，您有任何问题可以在GitHub上[提交issue](https://github.com/huaweicloud/huaweicloud-sdk-php-obs/issues)，或者在[华为云对象存储服务论坛](https://bbs.huaweicloud.com/forum/forum-620-1.html)中发帖求助。[接口参考文档](https://support.huaweicloud.com/api-obs_php_sdk_api_zh/obs_38_0001.html)详细介绍了每个接口的参数和使用方法。
对于较大文件上传，可以切分成段上传。用户可以在如下的应用场景内（但不仅限于此），使用分段上传的模式：
- 上传超过100MB大小的文件。
- 网络条件较差，和OBS服务端之间的链接经常断开。
- 上传前无法确定将要上传文件的大小。
分段上传包括如下步骤：
1. 初始化多段上传任务（ObsClient-\>initiateMultipartUpload）。
2. 逐个或并行上传段（ObsClient-\>uploadPart）。
3. 合并段（ObsClient-\>completeMultipartUpload）或取消多段上传任务(ObsClient-\>abortMultipartUpload)。
#### 初始化多段上传任务
使用分段上传方式传输数据前，必须先通知OBS初始化一个多段上传任务。该操作会返回一个OBS服务端创建的全局唯一标识（Upload ID），用于标识本次多段上传任务。您可以根据这个唯一标识来发起相关的操作，如取消多段上传任务、列举多段上传任务、列举已上传的段等。
您可以通过ObsClient-\>initiateMultipartUpload初始化一个多段上传任务：
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([ 
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
try {
    $resp = $obsClient->initiateMultipartUpload([
        'Bucket' => 'bucketname',
        'Key' => 'objectname',
        'ContentType' => 'text/plain',
        'Metadata' => ['property' => 'property-value']
    ]);
    printf("RequestId:%s\n",$resp['RequestId']);printf("UploadId:%s\n",$resp['UploadId']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("InitiateMultipartUploadFailed: Status:%d, Code:%s, Message:%s\n", $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    $obsClient->close();
}
```
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/note_3.0-zh-cn.png)
- 初始化多段上传任务时，除了指定上传对象的名称和所属桶外，您还可以使用**ContentType参数** 和**Metadata参数**分别指定对象MIME类型和对象自定义元数据。
- 调用初始化多段上传任务接口成功后，会返回多段上传任务的全局唯一标识（Upload ID），在后面的操作中将用到它。
 
#### 上传段
初始化一个多段上传任务之后，可以根据指定的对象名和Upload ID来分段上传数据。每一个上传的段都有一个标识它的号码------分段号（Part Number，范围是1\~10000）。对于同一个Upload ID，该分段号不但唯一标识这一段数据，也标识了这段数据在整个对象内的相对位置。如果您用同一个分段号上传了新的数据，那么OBS上已有的这个段号的数据将被覆盖。**除了最后一段以外，其他段的大小范围是100KB\~5GB**；最后段大小范围是0\~5GB。每个段不需要按顺序上传，甚至可以在不同进程、不同机器上上传，OBS会按照分段号排序组成最终对象。
您可以通过ObsClient-\>uploadPart上传段：
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([ 
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
try {
    $resp = $obsClient->uploadPart([
        'Bucket' => 'bucketname',
        'Key' => 'objectname',
        // 设置分段号，范围是1~10000
        'PartNumber' => 1,
        // 设置Upload ID
        'UploadId' => 'upload id from initiateMultipartUpload',
        // 设置将要上传的大文件,localfile为上传的本地文件路径，需要指定到具体的文件名
        'SourceFile' => 'localfile',
        // 设置分段大小
        'PartSize' => 5 * 1024 * 1024,
        // 设置分段的起始偏移大小
        'Offset' => 0
    ]);
    printf("RequestId:%s\n",$resp['RequestId']);
    printf("ETag:%s\n",$resp['ETag']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("UploadPartFailed: Status:%d, Code:%s, Message:%s\n", $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    $obsClient->close();
}
```
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/note_3.0-zh-cn.png)
- 使用**PartNumber参数** 指定分段号；使用**UploadId参数** 指定多段上传任务的全局唯一标识；使用**SourceFile参数** 指定待上传的文件；使用**PartSize参数** 指定分段大小；使用**Offset参数**指定分段的起始偏移大小。
- 上传段接口要求除最后一段以外，其他的段大小都要大于100KB。但是上传段接口并不会立即校验上传段的大小（因为不知道是否为最后一段）；只有调用合并段接口时才会校验。
- OBS会将服务端收到段数据的ETag值（段数据的MD5值）返回给用户。
- 可以通过**ContentMD5参数**设置上传数据的MD5值，提供给OBS服务端用于校验数据完整性。
- 分段号的范围是1\~10000。如果超出这个范围，OBS将返回400 Bad Request错误。
- 除最后一段外，其他段大小范围为 100KB**\~** 5GB（OBS 3.0 桶）或5MB**\~**5GB（OBS 2.0 桶）。建议在 OBS 3.0 桶上执行分段上传操作。
 
#### 合并段
所有分段上传完成后，需要调用合并段接口来在OBS服务端生成最终对象。在执行该操作时，需要提供所有有效的分段列表（包括分段号和分段ETag值）；OBS收到提交的分段列表后，会逐一验证每个段的有效性。当所有段验证通过后，OBS将把这些分段组合成最终的对象。
您可以通过ObsClient-\>completeMultipartUpload合并段：
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([ 
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
try {
    $resp = $obsClient->completeMultipartUpload([
        'Bucket' => 'bucketname',
        'Key' => 'objectname',
        // 设置Upload ID
        'UploadId' => 'upload id from initiateMultipartUpload',
        'Parts' => [
            ['PartNumber' => 1, 'ETag' => 'etag1'],
            ['PartNumber' => 2, 'ETag' => 'etag2']
        ]]);
    printf("RequestId:%s\n",$resp['RequestId']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("CompleteMultipartUploadFailed: Status:%d, Code:%s, Message:%s\n", $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    $obsClient->close();
}
```
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/note_3.0-zh-cn.png)
- 如果最后一个段之外的其它段大小过小（小于100KB），OBS返回400 Bad Request。
- 使用**UploadId参数** 指定多段上传任务的全局唯一标识；使用**Parts参数**指定分段号与分段ETag值的列表，该列表必须按分段号升序排列。
- 分段可以是不连续的。
 
#### 取消多段上传任务
多段上传任务可以被取消，当一个多段上传任务被取消后，就不能再使用其Upload ID做任何操作，已经上传段也会被OBS删除。
采用分段上传方式上传对象过程中或上传对象失败后会在桶内产生段，这些段会占用您的存储空间，您可以通过取消该多段上传任务来清理掉不需要的段，节约存储空间。
您可以通过ObsClient-\>abortMultipartUpload取消多段上传任务：
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([ 
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
try {
    $resp = $obsClient->abortMultipartUpload([
        'Bucket' => 'bucketname',
        'Key' => 'objectname',
        // 设置Upload ID
        'UploadId' => 'upload id from initiateMultipartUpload'
    ]);
    printf("RequestId:%s\n",$resp['RequestId']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("AbortMultipartUploadFailed: Status:%d, Code:%s, Message:%s\n", $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    $obsClient->close();
}
```
#### 列举已上传的段
您可使用ObsClient-\>listParts列举出某一多段上传任务所有已经上传成功的段。
该接口可设置的参数如下：
| **参数**          | **作用**                                                           |
|:---|:---|
| UploadId         | 多段上传任务全局唯一标识，从ObsClient-\>initiateMultipartUpload返回的结果获取。       |
| MaxParts          | 表示列举已上传段的返回结果最大段数目，即分页时每一页中段数目，MaxParts 默认值 1000，取值范围 1\~1000。 |
| PartNumberMarker | 表示列举已上传段的起始位置，只有Part Number大于该参数的段会被列出，PartNumberMarker 默认为空。    |
   
- 简单列举
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([ 
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
try {
    $resp = $obsClient->listParts([
        'Bucket' => 'bucketname',
        'Key' => 'objectname',
        'UploadId' => 'upload id from initiateMultipartUpload'
    ]);
    printf( "RequestId:%s\n", $resp['RequestId']);
    foreach ($resp['Parts'] as $index => $part) {
        printf( "Parts[%d]\n", $index + 1 );
        // 分段号，上传时候指定
        printf( "PartNumber:%s\n", $part ['PartNumber']);
        // 段的最后上传时间
        printf( "LastModified:%s\n", $part ['LastModified']);
        // 分段的ETag值
        printf( "ETag:%s\n", $part ['ETag']);
        // 段数据大小
        printf( "Size:%s\n", $part ['Size']);
    }
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("ListPartsFailed: Status:%d, Code:%s, Message:%s\n", $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    $obsClient->close();
}
```
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/note_3.0-zh-cn.png)
- 列举段至多返回1000个段信息，如果指定的Upload ID包含的段数量大于1000，则返回结果中IsTruncated为true表明本次没有返回全部段，并可通过NextPartNumberMarker获取下次列举的起始位置。
- 如果想获取指定Upload ID包含的所有分段，可以采用分页列举的方式。
 
- 列举所有段
由于ObsClient-\>listParts只能列举至多1000个段，如果段数量大于1000，列举所有分段请参考如下示例：
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
// 列举已上传的段
try {
    $partNumberMarker = null;
    $index = 1;
    do{
        $resp = $obsClient->listParts([
            'Bucket' => 'bucketname',
            'Key' => 'objectname',
            'UploadId' => 'upload id from initiateMultipartUpload',
            'PartNumberMarker' => $partNumberMarker
        ]);
    printf( "RequestId:%s\n", $resp['RequestId']);
    foreach ($resp['Parts'] as $part) {
        printf( "Parts[%d]\n", $index );
        // 分段号，上传时候指定
        printf( "PartNumber:%s\n", $part['PartNumber']);
        // 段的最后上传时间
        printf( "LastModified:%s\n", $part['LastModified']);
        // 分段的ETag值
        printf( "ETag:%s\n", $part['ETag']);
        // 段数据大小
        printf( "Size:%s\n", $part['Size']);
        $index ++;
    }
    $partNumberMarker = $resp['NextPartNumberMarker'];
    }while($resp['IsTruncated']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("ListPartsFailed: Status:%d, Code:%s, Message:%s\n",
        $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    // 关闭ObsClient，释放资源
    $obsClient->close();
}
```
#### 列举多段上传任务
您可以通过ObsClient-\>listMultipartUploads列举多段上传任务。列举多段上传任务可设置的参数如下：
| **参数**         | **作用**                                                                                                                             |
|:---|:---|
| Prefix         | 限定返回的多段上传任务中的对象名必须带有Prefix前缀。                                                                                                       |
| Delimiter       | 用于对多段上传任务中的对象名进行分组的字符。对于对象名中包含Delimiter的任务，其对象名（如果请求中指定了Prefix，则此处的对象名需要去掉Prefix）中从首字符至第一个Delimiter之间的字符串将作为一个分组并作为CommonPrefix返回。 |
| MaxUploads      | 列举多段上传任务的最大数目，取值范围为1\~1000，当超出范围时，按照默认的1000进行处理。                                                                                  |
| KeyMarker        | 表示列举时返回指定的KeyMarker之后的多段上传任务。                                                                                                        |
| UploadIdMarker | 只有与KeyMarker参数一起使用时才有意义，用于指定返回结果的起始位置，即列举时返回指定KeyMarker的UploadIdMarker之后的多段上传任务。                                                 |
   
- 简单列举多段上传任务
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
// 列举分段上传任务
try {
    $index = 1;
    $resp = $obsClient->listMultipartUploads([
        'Bucket' => 'bucketname'
    ]);
    printf( "RequestId:%s\n", $resp['RequestId']);
    foreach ($resp['Uploads'] as $index => $upload) {
        printf( "Uploads[%d]\n", $index );
        printf( "Key:%s\n", $upload['Key']);
        printf( "UploadId:%s\n", $upload['UploadId']);
        printf( "Initiated:%s\n", $upload['Initiated']);
        printf( "Owner[ID]:%s\n", $upload['Owner']['ID']);}
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("ListMultipartUploadsFailed: Status:%d, Code:%s, Message:%s\n",
        $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    // 关闭ObsClient，释放资源
    $obsClient->close();
}
```
![](https://support.huaweicloud.com/sdk-php-devg-obs/public_sys-resources/note_3.0-zh-cn.png)
- 列举多段上传任务至多返回1000个任务信息，如果指定的桶包含的多段上传任务数量大于1000，则返回结果中IsTruncated为true表明本次没有返回全部结果，并可通过NextKeyMarker和NextUploadIdMarker获取下次列举的起点。
- 如果想获取指定桶包含的所有多段上传任务，可以采用分页列举的方式。
 
- 列举全部多段上传任务
```
<?php
// 引入依赖库
require 'vendor/autoload.php';
// 使用源码安装时引入SDK代码库
// require 'obs-autoloader.php';
// 声明命名空间
use Obs\ObsClient;
use Obs\ObsException;
// 创建ObsClient实例
$obsClient = new ObsClient([
    //推荐通过环境变量获取访问密钥AK/SK，这里也可以使用其他外部引入方式传入，如果使用硬编码可能会存在泄露风险。
    //您可以登录访问管理控制台获取访问密钥AK/SK，获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
    'key' => getenv('ACCESS_KEY_ID'),
    'secret' => getenv('SECRET_ACCESS_KEY'),
    'endpoint' => 'https://your-endpoint',
]);
// 分页列举全部多段上传对象
try {
    $keyMarker = null;
    $uploadIdMarker = null;
    $index = 1;
    do {
        $resp = $obsClient->listMultipartUploads([
            'Bucket' => 'bucketname',
            'KeyMarker' => $keyMarker,
            'UploadIdMarker' => $uploadIdMarker
        ]);
        printf("RequestId:%s\n", $resp['RequestId']);
        foreach ($resp['Uploads'] as $index => $upload) {
            printf("Uploads[%d]\n", $index);
            printf("Key:%s\n", $upload['Key']);
            printf("UploadId:%s\n", $upload['UploadId']);
            printf("Initiated:%s\n", $upload['Initiated']);
            printf("Owner[ID]:%s\n", $upload['Owner']['ID']);
            printf("StorageClass:%s\n", $upload['StorageClass']);
            $index++;
        }
        $keyMarker = $resp['NextKeyMarker'];
        $uploadIdMarker = $resp['NextUploadIdMarker'];
    } while ($resp['IsTruncated']);
} catch (ObsException $e) {
    // 操作失败，获取详细异常信息
    printf("ListMultipartUploadsFailed: Status:%d, Code:%s, Message:%s\n",
        $e->getStatusCode(), $e->getExceptionCode(), $e->getExceptionMessage());
} finally {
    // 关闭ObsClient，释放资源
    $obsClient->close();
}
```
