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
遇到的問題
- 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];
}