OSS提供的分片上传(Multipart Upload)功能,将要上传的较大的文件(Object)分成多个数据块(OSS中称之为Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。
分片上传流程
分片上传的基本流程如下:
- 将要上传的文件按照一定的大小分片。
- 初始化一个分片上传任务(InitiateMultipartUpload)。
- 逐个或并行上传分片(UploadPart)。
- 完成上传(CompleteMultipartUpload)。

该过程需注意以下几点:
- 除了最后一块Part,其他Part的大小不能小于100 KB,否则会导致调用CompleteMultipartUpload接口失败。
- 要上传的文件切分成Part之后,文件顺序是通过上传过程中指定的partNumber来确定的,实际执行中并没有顺序要求,因此可以实现并发上传。
具体的并发个数并不是越多速度越快,要结合用户自身的网络情况和设备负载综合考虑。网络情况较好时,建议增大分片大小。反之,减小分片大小。
- 默认情况下,已经上传但还没有调用CompleteMultipartUpload的Part是不会被自动回收的,因此如果要终止上传并删除占用的空间请调用AbortMultipartUpload。如果需要自动回收上传的Part,请参见生命周期管理。
初始化分片上传
以下代码用于初始化分片上传。
__block NSString * uploadId = nil;
__block NSMutableArray * partInfos = [NSMutableArray new];
NSString * uploadToBucket = @"<bucketName>";
// objectKey等同于objectName,表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg
NSString * uploadObjectkey = @"<objectKey>";
// OSSInitMultipartUploadRequest用于指定上传文件的名称以及上传文件所属的存储空间的名称。
OSSInitMultipartUploadRequest * init = [OSSInitMultipartUploadRequest new];
init.bucketName = uploadToBucket;
init.objectKey = uploadObjectkey;
// init.contentType = @"application/octet-stream";
// multipartUploadInit返回的结果中包含UploadId,UploadId是分片上传的唯一标识。
OSSTask * initTask = [client multipartUploadInit:init];
[initTask waitUntilFinished];
if (!initTask.error) {
OSSInitMultipartUploadResult * result = initTask.result;
uploadId = result.uploadId;
} else {
NSLog(@"multipart upload failed, error: %@", initTask.error);
return;
}
上传分片
以下代码用于上传分片。
// 指定要上传的文件。
NSString * filePath = [docDir stringByAppendingPathComponent:@"***"];
// 获取文件大小。
uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize];
// 设置分片上传数量。
int chuckCount = *;
// 设置分片大小。
uint64_t offset = fileSize/chuckCount;
for (int i = 1; i <= chuckCount; i++) {
OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
uploadPart.bucketName = uploadToBucket;
uploadPart.objectkey = uploadObjectkey;
uploadPart.uploadId = uploadId;
uploadPart.partNumber = i; // part number start from 1
NSFileHandle* readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
[readHandle seekToFileOffset:offset * (i -1)];
NSData* data = [readHandle readDataOfLength:offset];
uploadPart.uploadPartData = data;
OSSTask * uploadPartTask = [client uploadPart:uploadPart];
[uploadPartTask waitUntilFinished];
if (!uploadPartTask.error) {
OSSUploadPartResult * result = uploadPartTask.result;
uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:uploadPart.uploadPartFileURL.absoluteString error:nil] fileSize];
[partInfos addObject:[OSSPartInfo partInfoWithPartNum:i eTag:result.eTag size:fileSize]];
} else {
NSLog(@"upload part error: %@", uploadPartTask.error);
return;
}
}
上述代码调用uploadPart
来上传每一个分片。
- 每一个分片上传请求需指定UploadId和PartNum。
uploadPart
接口并不会立即校验上传。只有完成分片上传时才会校验Part的大小。- Part编号范围是1~10000。如果超出该范围,OSS将返回InvalidArgument的错误码。
- 每次上传Part时都要把流定位到此次上传片开头所对应的位置。
- 每次上传Part之后,OSS的返回结果中会包含每个分片的ETag值,ETag值为Part数据MD5值,您需要将ETag值和Part编号组合成PartETag并保存,用于后续完成分片上传。
完成分片上传
以下代码中的partInfos
为分片上传过程中保存的PartETag列表,OSS收到您提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,OSS会将这些Part组合成一个完整的文件。
以下代码用于完成分片上传。
OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
complete.bucketName = uploadToBucket;
complete.objectKey = uploadObjectkey;
complete.uploadId = uploadId;
complete.partInfos = partInfos;
OSSTask * completeTask = [client completeMultipartUpload:complete];
[[completeTask continueWithBlock:^id(OSSTask *task) {
if (!task.error) {
OSSCompleteMultipartUploadResult * result = task.result;
// ...
} else {
// ...
}
return nil;
}] waitUntilFinished];
完成分片上传请求时可以设置servercallback
函数,请求完成后会向指定的Server Address发送回调请求。您可以通过返回结果中的result.serverReturnJsonString
查看servercallback
结果。
OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
complete.bucketName = @"<bucketName>";
complete.objectKey = @"<objectKey>";
complete.uploadId = uploadId;
complete.partInfos = partInfos;
complete.callbackParam = @{
@"callbackUrl": @"<server address>",
@"callbackBody": @"<test>"
};
complete.callbackVar = @{
@"var1": @"value1",
@"var2": @"value2"
};
OSSTask * completeTask = [client completeMultipartUpload:complete];
[[completeTask continueWithBlock:^id(OSSTask *task) {
if (!task.error) {
OSSCompleteMultipartUploadResult * result = task.result;
NSLog(@"server call back return : %@", result.serverReturnJsonString);
} else {
// ...
}
return nil;
}] waitUntilFinished];
如果要校验分片上传到OSS的文件和本地文件是否一致,可以在上传文件时携带文件的Content-MD5值,OSS服务器会帮助您进行Content-MD5校验。只有在OSS服务器接收到的文件MD5值和Content-MD5一致时才可以上传成功,从而保证上传文件的数据完整性。
以下代码用于MD5校验设置。
OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
uploadPart.bucketName = TEST_BUCKET;
uploadPart.uploadId = uploadId;
....
uploadPart.contentMd5 = [OSSUtil fileMD5String:filepath];
列举分片
调用listParts
方法获取某个上传事件所有已上传的分片。
OSSListPartsRequest * listParts = [OSSListPartsRequest new];
listParts.bucketName = @"<bucketName>";
listParts.objectKey = @"<objectkey>";
listParts.uploadId = @"<uploadid>";
OSSTask * listPartTask = [client listParts:listParts];
[listPartTask continueWithBlock:^id(OSSTask *task) {
if (!task.error) {
NSLog(@"list part result success!");
OSSListPartsResult * listPartResult = task.result;
for (NSDictionary * partInfo in listPartResult.parts) {
NSLog(@"each part: %@", partInfo);
}
} else {
NSLog(@"list part result error: %@", task.error);
}
return nil;
}];
取消分片上传
以下代码用于取消了对应UploadId
的分片上传请求。
OSSAbortMultipartUploadRequest * abort = [OSSAbortMultipartUploadRequest new];
abort.bucketName = @"<bucketName>";
abort.objectKey = @"<objectKey>";
abort.uploadId = uploadId;
OSSTask * abortTask = [client abortMultipartUpload:abort];
[abortTask waitUntilFinished];
if (!abortTask.error) {
OSSAbortMultipartUploadResult * result = abortTask.result;
uploadId = result.uploadId;
} else {
NSLog(@"multipart upload failed, error: %@", abortTask.error);
return;
}