更新时间:2026-08-28 GMT+08:00
分享

批量复制对象

本示例用于批量复制OBS中的对象,支持同桶复制和跨桶复制。您可以指定源对象Key和目标对象Key的对应关系,以指定并发数并发执行复制操作,并可选保留源对象元数据或替换为新元数据,适用于数据迁移、跨区域备份等场景。

#import <Foundation/Foundation.h>
#import <OBS/OBS.h>
#pragma mark - Result Model
@interface BatchCopyResult : NSObject
@property (nonatomic, copy) NSString *srcObjectKey;
@property (nonatomic, copy) NSString *dstObjectKey;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, copy) NSString *etag;
@end
@implementation BatchCopyResult
@end
#pragma mark - Batch Copier
@interface BatchCopier : NSObject
@property (nonatomic, strong) OBSClient *client;
@property (nonatomic, copy) NSString *srcBucketName;
@property (nonatomic, copy) NSString *dstBucketName;
@property (nonatomic, assign) NSInteger concurrentCount;
@property (nonatomic, assign) OBSMetaDirective metadataDirective;
@property (nonatomic, strong) NSDictionary<NSString *, NSString *> *customMetadata;
@property (nonatomic, strong) NSMutableArray<BatchCopyResult *> *results;
@property (nonatomic, strong) dispatch_queue_t resultQueue;
- (instancetype)initWithClient:(OBSClient *)client
                 srcBucketName:(NSString *)srcBucketName
                 dstBucketName:(NSString *)dstBucketName
               concurrentCount:(NSInteger)concurrentCount;
- (void)copyObjects:(NSArray<NSString *> *)srcObjectKeys
       toObjectKeys:(NSArray<NSString *> *)dstObjectKeys
      waitUntilDone:(BOOL)wait;
@end
@implementation BatchCopier
- (instancetype)initWithClient:(OBSClient *)client
                 srcBucketName:(NSString *)srcBucketName
                 dstBucketName:(NSString *)dstBucketName
               concurrentCount:(NSInteger)concurrentCount {
    self = [super init];
    if (self) {
        _client = client;
        _srcBucketName = srcBucketName;
        _dstBucketName = dstBucketName;
        _concurrentCount = MIN(concurrentCount > 0 ? concurrentCount : 3, 10);
        _metadataDirective = OBSMetaDirectiveCopy;
        _results = [NSMutableArray array];
        _resultQueue = dispatch_queue_create("com.obs.batchcopy.result", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
- (void)copyObjects:(NSArray<NSString *> *)srcObjectKeys
       toObjectKeys:(NSArray<NSString *> *)dstObjectKeys
      waitUntilDone:(BOOL)wait {
    if (srcObjectKeys.count != dstObjectKeys.count) {
        NSLog(@"[BatchCopy] ERROR: source and destination key arrays must have the same count");
        return;
    }
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(self.concurrentCount);
    dispatch_queue_t queue = dispatch_queue_create("com.obs.batchcopy", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"[BatchCopy] Starting %lu copy operations, concurrency: %ld, src: %@ -> dst: %@",
          (unsigned long)srcObjectKeys.count, (long)self.concurrentCount, self.srcBucketName, self.dstBucketName);
    for (NSUInteger i = 0; i < srcObjectKeys.count; i++) {
        NSString *srcKey = srcObjectKeys[i];
        NSString *dstKey = dstObjectKeys[i];
        if (srcKey.length == 0 || dstKey.length == 0) {
            continue;
        }
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
            [self copyObjectFrom:srcKey to:dstKey completion:^{
                dispatch_semaphore_signal(semaphore);
                dispatch_group_leave(group);
            }];
        });
    }
    if (wait) {
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
        [self printSummary];
    }
}
- (void)copyObjectFrom:(NSString *)srcObjectKey
                    to:(NSString *)dstObjectKey
             completion:(void (^)(void))completion {
    // 注意:copyObject不做源对象存在性预检,源不存在时服务端返回404
    OBSCopyObjectRequest *request = [[OBSCopyObjectRequest alloc] initWithSrcBucketName:self.srcBucketName
                                                                          srcObjectKey:srcObjectKey
                                                                          dstBucketName:self.dstBucketName
                                                                           dstObjectKey:dstObjectKey];
    // 注意:如需替换元数据,需设metadataDirective=OBSMetaDirectiveReplace并设customMetadata
    request.dstObjectMetaDirective = self.metadataDirective;
    if (self.metadataDirective == OBSMetaDirectiveReplace && self.customMetadata.count > 0) {
        request.metaDataDict = [self.customMetadata mutableCopy];
    }
    __weak typeof(self) weakSelf = self;    __strong typeof(weakSelf) strongSelf = weakSelf;    if (!strongSelf) return;
    [self.client copyObject:request completionHandler:^(OBSCopyObjectResponse *response, NSError *error) {
        BatchCopyResult *result = [[BatchCopyResult alloc] init];
        result.srcObjectKey = srcObjectKey;
        result.dstObjectKey = dstObjectKey;
        result.error = error;
        result.success = (error == nil);
        result.etag = response.etag;
        if (error) {
            NSLog(@"[Copy] FAILED - %@ -> %@ : %@", srcObjectKey, dstObjectKey, error.localizedDescription);
        } else {
            NSLog(@"[Copy] SUCCESS - %@ (%@) -> %@ (%@)",
                  srcObjectKey, strongSelf.srcBucketName, dstObjectKey, strongSelf.dstBucketName);
        }
        dispatch_sync(strongSelf.resultQueue, ^{ [strongSelf.results addObject:result]; });
        if (completion) completion();
    }];
}
- (void)printSummary {
    NSInteger success = 0, failure = 0;
    for (BatchCopyResult *r in self.results) {
        if (r.success) success++;
        else failure++;
    }
    NSLog(@"\n========== Batch Copy Summary ==========");
    NSLog(@"Total: %lu | Success: %ld | Failed: %ld", (unsigned long)self.results.count, (long)success, (long)failure);
    if (failure > 0) {
        NSLog(@"Failed items:");
        for (BatchCopyResult *r in self.results) {
            if (!r.success) {
                NSLog(@"  - %@ -> %@ : %@", r.srcObjectKey, r.dstObjectKey, r.error.localizedDescription);
            }
        }
    }
    NSLog(@"=========================================\n");
}
@end
#pragma mark - Entry Point
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // === Configuration ===
        NSString *endPoint = @"https://your-endpoint";
        // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量AccessKeyID和SecretAccessKey。
        // 您可以登录访问管理控制台获取访问密钥AK/SK,获取方式请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html
        char* ak_env = getenv("AccessKeyID");
        char* sk_env = getenv("SecretAccessKey");
        if (ak_env == NULL || sk_env == NULL) {
            NSLog(@"Error: AccessKeyID or SecretAccessKey environment variable not set");
            return 1;
        }
        NSString *AK = [NSString stringWithUTF8String:ak_env];
        NSString *SK = [NSString stringWithUTF8String:sk_env];
        // Source and destination buckets
        // Set dstBucketName equal to srcBucketName for in-bucket copy
        NSString *srcBucketName = @"your-source-bucket";
        NSString *dstBucketName = @"your-destination-bucket";
        NSInteger concurrentCount = 3;  // Adjust as needed
        // Source object keys
        NSArray<NSString *> *srcObjectKeys = @[
            @"source_folder/file1.txt",
            @"source_folder/file2.jpg",
            @"source_folder/file3.pdf",
            @"source_folder/file4.mp4",
            @"source_folder/file5.zip"
        ];
        // Destination object keys (must match srcObjectKeys count)
        NSString *dstPrefix = @"backup/";
        NSMutableArray<NSString *> *dstObjectKeys = [NSMutableArray arrayWithCapacity:srcObjectKeys.count];
        for (NSString *srcKey in srcObjectKeys) {
            // 注意:lastPathComponent只取文件名,丢失中间目录结构
            [dstObjectKeys addObject:[dstPrefix stringByAppendingString:[srcKey lastPathComponent]]];
        }
        // === Initialize OBS Client ===
        OBSStaticCredentialProvider *credentialProvider = [[OBSStaticCredentialProvider alloc] initWithAccessKey:AK secretKey:SK];
        OBSServiceConfiguration *conf = [[OBSServiceConfiguration alloc] initWithURLString:endPoint credentialProvider:credentialProvider];
        OBSClient *client = [[OBSClient alloc] initWithConfiguration:conf];
        // === Batch Copy ===
        BatchCopier *copier = [[BatchCopier alloc] initWithClient:client
                                                  srcBucketName:srcBucketName
                                                  dstBucketName:dstBucketName
                                                concurrentCount:concurrentCount];
        // OBSMetaDirectiveCopy: keep source object's metadata (default)
        // OBSMetaDirectiveReplace: use new metadata (set via customMetadata below)
        copier.metadataDirective = OBSMetaDirectiveCopy;
        // Optional: set custom metadata when using OBSMetaDirectiveReplace
        // copier.customMetadata = @{@"X-Obs-Meta-Copied-By": @"BatchCopyTest", @"X-Obs-Meta-Copy-Time": [[NSDate date] description]};
        [copier copyObjects:srcObjectKeys toObjectKeys:dstObjectKeys waitUntilDone:YES];
    }
    return 0;
}

相关文档