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; }];
注意 默认情况下,如果存储空间中的分片上传事件的数量大于1000,则OSS仅返回1000个Multipart Upload信息,且返回结果中IsTruncated的值为false,并返回NextPartNumberMarker作为下次读取的起点。
取消分片上传
以下代码用于取消了对应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; }