iOS OSS上傳文件問題

iOS OSS上傳文件問題

最近接入OSS SDK做資源上傳到OSS,期間遇到的一些問題,做一下記錄

1. OSS SDK接入

選擇接入OSS SDK而不是直接拼form表單去上傳文件,主要是SDK提供了一系列的功能,實(shí)現(xiàn)起來存在一定成本;SDK內(nèi)部提供了并發(fā)上傳、大文件分批上傳、斷點(diǎn)續(xù)傳、上傳下載進(jìn)度管理等
接入SDK直接使用pod的方式

pod 'AliyunOSSiOS'
  • 一般文件上傳,使用OSSPutObjectRequest
  • 大文件分批上傳,使用OSSMultipartUploadRequest

2. 上傳和下載文件

官方的demo也有示例,可以參照著做一些修改和完善

由于OSS文件存儲(chǔ)是收費(fèi)的,所以我們一般在管理OSS的bucket的時(shí)候需要設(shè)置權(quán)限(公共讀、公共讀寫、私有),官方建議是設(shè)置成私有權(quán)限,避免被人惡意寫入或者私密的文件被獲取

  • 公共讀:未授權(quán)用戶只有讀權(quán)限,寫入操作需要授權(quán)
  • 公共讀寫:所有的用戶都可以讀寫
  • 私有:只有授權(quán)的用戶才有讀寫權(quán)限

需要鑒權(quán)的流程:獲取授權(quán)(服務(wù)端下發(fā)一個(gè)臨時(shí)用戶的鑒權(quán)信息)-- 根據(jù)鑒權(quán)信息進(jìn)行簽名然后發(fā)起請(qǐng)求 -- OSS服務(wù)校驗(yàn) -- 返回結(jié)果

公共讀寫權(quán)限的則不需要鑒權(quán)流程

配置client

根據(jù)服務(wù)端下發(fā)的配置信息,初始化OSSClient,需要注意的是設(shè)置Client的endpoint的時(shí)候用endpointExceptBucket(如果服務(wù)端返回的endpoint中包含了bucket信息,那么就截取掉)

- (void)configClient {
    OSSStsTokenCredentialProvider *provider = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:self.configuration.accessKeyId
                                                                                             secretKeyId:self.configuration.accessKeySecret
                                                                                           securityToken:self.configuration.securityToken];
    _defaultClient = [[OSSClient alloc] initWithEndpoint:self.configuration.endpointExceptBucket credentialProvider:provider];
}

- (NSString *)endpointExceptBucket {
    NSString *needReplaceString = [NSString stringWithFormat:@"%@.", _ossBucket];
    _endpointExceptBucket = [[_endpoint stringByReplacingOccurrencesOfString:needReplaceString withString:@""] oss_trim];
    
    return _endpointExceptBucket;
}

上傳文件

- (void)asyncPutFile:(NSString *)objectKey localFilePath:(NSString *)filePath progress:(OSSProgressBlock _Nullable)uploadProgress success:(void (^ _Nullable)(CassOSSUploadResult * _Nonnull))success failure:(void (^ _Nullable)(NSError * _Nonnull, NSString * _Nonnull))failure {
    if (![objectKey oss_isNotEmpty]) {
        NSError *error = [NSError errorWithDomain:NSInvalidArgumentException code:0 userInfo:@{NSLocalizedDescriptionKey: @"objectKey should not be nil"}];
        failure(error, filePath);
        return;
    }
    
    _normalUploadRequest = [OSSPutObjectRequest new];
    _normalUploadRequest.bucketName = self.configuration.ossBucket;
    _normalUploadRequest.objectKey = objectKey;
    _normalUploadRequest.uploadingFileURL = [NSURL fileURLWithPath:filePath];
    _normalUploadRequest.isAuthenticationRequired = YES;
    _normalUploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
        dispatch_async(dispatch_get_main_queue(), ^{
            CGFloat progress = 1.f * totalByteSent / totalBytesExpectedToSend;
            if (uploadProgress) {
                uploadProgress(progress);
            }
        });
    };
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSTask * task = [self.defaultClient putObject:self.normalUploadRequest];
        [task continueWithBlock:^id(OSSTask *task) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (task.error) {
                    if (failure) {
                        failure(task.error, filePath);
                    }
                } else {
                    if (success) {
                        NSString *ossFileURL = [NSString stringWithFormat:@"%@/%@", self.configuration.endpoint, objectKey];
                        CassOSSUploadResult *result = [CassOSSUploadResult new];
                        result.fileURL = ossFileURL;
                        result.fileLocalPath = filePath;
                        success(result);
                    }
                }
            });
            
            return nil;
        }];
    });
}

objectKey一般是一個(gè)OSS的文件路徑+上傳的文件名;?? Object的命名規(guī)范:使用UTF-8編碼,長(zhǎng)度必須在1-1023字節(jié)之間,不能以“/”或者“\”字符開頭,中間的路徑也不能多"/"分隔符 例如: image/user//xxx/xxx.png,中間多了分隔符
bucketName 只能包括小寫字母、數(shù)字和短橫線(-),必須以小寫字母或者數(shù)字開頭,長(zhǎng)度必須在3-63字節(jié)之間。
isAuthenticationRequired這個(gè)屬性需要重點(diǎn)關(guān)注,默認(rèn)是YES是需要做校驗(yàn)的,如果設(shè)置了私有讀權(quán)限那么這個(gè)值就設(shè)置為YES,否則會(huì)校驗(yàn)失??;下載的時(shí)候這個(gè)值如果不是私有寫權(quán)限,這個(gè)設(shè)置為NO,否則會(huì)校驗(yàn)失敗。

下載文件

方式跟上傳文件類似,使用OSSGetObjectRequest

遇到的問題

  1. AccessDenied
Error Domain=com.aliyun.oss.serverError Code=-403 "(null)" UserInfo={__name=Error, HostId=xxx.oss-cn-shenzhen.aliyuncs.com, Message=You have no right to access this object because of bucket acl., Code=AccessDenied, RequestId=5D5217E3AA5E736DC2B7DB02}

這種是服務(wù)端校驗(yàn)權(quán)限失敗導(dǎo)致的,子用戶/臨時(shí)用戶沒有訪問Object的權(quán)限(如putObject getObject、appendObject deleteObject、postObject)等

解決方法:

  • 檢查用戶的權(quán)限是否正確,權(quán)限問題排查
  • 檢查bucket設(shè)置的權(quán)限(公共讀、公共讀寫、私有),根據(jù)權(quán)限來設(shè)置調(diào)用接口時(shí)isAuthenticationRequired屬性的值,需要權(quán)限的就設(shè)置為YES,不需要的設(shè)置為NO

在開發(fā)的過程中,bucket是設(shè)置的公共讀權(quán)限,調(diào)用的時(shí)候雖然設(shè)置了client的credentialProvider,但是由于設(shè)置isAuthenticationRequired時(shí)候設(shè)置的是NO,導(dǎo)致報(bào)上述錯(cuò)誤,這個(gè)就是由于沒有閱讀sdk的源碼,在創(chuàng)建task的時(shí)候會(huì)根據(jù)設(shè)置的值去設(shè)置interceptors,源碼如下:

- (OSSTask *)invokeRequest:(OSSNetworkingRequestDelegate *)request requireAuthentication:(BOOL)requireAuthentication {
    /* if content-type haven't been set, we set one */
    if (!request.allNeededMessage.contentType.oss_isNotEmpty
        && ([request.allNeededMessage.httpMethod isEqualToString:@"POST"] || [request.allNeededMessage.httpMethod isEqualToString:@"PUT"])) {

        request.allNeededMessage.contentType = [OSSUtil detemineMimeTypeForFilePath:request.uploadingFileURL.path               uploadName:request.allNeededMessage.objectKey];
    }

    // Checks if the endpoint is in the excluded CName list.
    [self.clientConfiguration.cnameExcludeList enumerateObjectsUsingBlock:^(NSString *exclude, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([self.endpoint hasSuffix:exclude]) {
            request.allNeededMessage.isHostInCnameExcludeList = YES;
            *stop = YES;
        }
    }];

    id<OSSRequestInterceptor> uaSetting = [[OSSUASettingInterceptor alloc] initWithClientConfiguration:self.clientConfiguration];
    [request.interceptors addObject:uaSetting];

    /* check if the authentication is required */
    if (requireAuthentication) {
        id<OSSRequestInterceptor> signer = [[OSSSignerInterceptor alloc] initWithCredentialProvider:self.credentialProvider];
        [request.interceptors addObject:signer];
    }

    request.isHttpdnsEnable = self.clientConfiguration.isHttpdnsEnable;

    return [_networking sendRequest:request];
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容