对象存储OSS-iOS

对象存储OSS-iOS

这里主要调研了一下文件上传

文档地址

常见问题

使用前请确保先开通的OSS服务,并创建一个Bucket。

一、基础解读

1、相关名词

Bucket 存储空间

Region 地域

Endpoint 访问域名

AccessKey 简称AK,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret

2、OSS的上传和下载

  • OSS提供了多种类型的上传文件的方法,如使用单次PUT请求完成的简单上传,使用网页表单直接上传的表单上传,用于大文件上传的分片上传,以及适用于视频监控等领域的追加上传。

  • 同样也可以使用多种方法从OSS下载文件,如简单下载和下载大文件的断点续传下载。

3、基于SDK快速开始

1、开通OSS后,从控制台上获取AccessKeyId和AccessKeySecret

2、下载各种开发语言SDK

3、根据SDK的文档描述,完成上传、下载文件等操作

一般的客户端的AccessKeyId和AccessKeySecret我们通过请求AppServer来获取。

4、基于OSS的移动开发

文档地址

文档中有个流程图画的特别明确而且有很有想法。

4.1 整体流程
4.1.1、客户端申请STS凭证

客户端请求AppServer,从AppServer获取上传需要的AccessKeyId和AccessKeySecret等信息

4.1.2、AppServer请求AssumeRole

服务器请求AssumeRole,是阿里的一个服务,也别管它是什么了,对客户端来说没意义。

这个服务里进行鉴定等安全验证的操作,可能还有其他吧。

4.1.3、AssumeRole返回STS凭证

STS凭证,即使用STS方式上传文件所需要的AccessKeyId和AccessKeySecret等信息。

4.1.4、AppServer返回STS到客户端

最终将STS信息返回到客户端。

4.1.5、客户端使用STS授权信息上传文件

客户端调用对应的SDK,使用SDK的Api,上传文件到AssumeRole。

这里是直接上传到阿里的AssumeRole服务里,AssumeRole将文件存储到OSS对应的Bucket下。

4.1.6、AssumeRole返回上传结果到客户端

AssumeRole将上传结果返回到SDK,通过SDK返回到客户端的App上。

4.2 简化流程

对于客户端来说,简化一下流程是有必要的,因为多余的我们不用关心。

4.2.1、请求AppServer获取STS权限

获取AccessKeyId和AccessKeySecret等信息。

4.2.2、将STS授权信息通过OSS SDK设置到SDK里

设置了才能有权限访问上传的Api

4.2.3、调用OSS SDK进行文件上传,SDK返回结果等回调

5、注意

5.1 关于授权

客户端不需要每次都向应用服务器请求授权,在第一次授权完成之后可以缓存STS返回的临时凭证直到超过失效时间。

5.2 iOS13视频上传

上传会报错,具体解决办法请看参考

二、对象存储-iOS

文档地址

1、基本流程

1.1 集成SDK

需要引入的系统库有:libresolve.tbd,libresolve.9.tbd,CoreText.framework,MobileCoreServices.framework,SystermConfiguration.framework,libz.tbd

哪些库不需要自己试试吧。

1.2 引入头文件

#import <AliyunOSSiOS/OSSService.h>

1.3 尝试初始化OSSClient

从AppServer获取Bucket等信息,用于初始化OSSClient

[VHUploaderClient requestUploadAuthKeyWithToken:accessToken success:^(id  _Nullable responseObject) {
    
    OSSStsTokenCredentialProvider *credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:responseObject[@"stsArr"][@"AccessKeyId"] secretKeyId:responseObject[@"stsArr"][@"AccessKeySecret"] securityToken:responseObject[@"stsArr"][@"SecurityToken"]];
    
    self.client = [[OSSClient alloc] initWithEndpoint:kEndPoint credentialProvider:credential];

    self.bucketName = responseObject[@"oss_bucket"];
    
} failure:^(NSError *error) {
    
}];
1.4 上传
OSSPutObjectRequest * put = [[OSSPutObjectRequest alloc] init];
// 必填字段
put.bucketName = self.bucketName;
put.objectKey = @"测试视频";
put.uploadingFileURL = [NSURL fileURLWithPath:filePath];
// put.uploadingData = <NSData *>; // 直接上传NSData
// 可选字段,可不设置
put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
    // 当前上传段长度、当前已经上传总长度、一共需要上传的总长度
    NSLog(@"uploadProgress %lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
};

// 以下可选字段的含义参考: https://docs.aliyun.com/#/pub/oss/api-reference/object&PutObject
//put.contentType = @"";
// put.contentMd5 = @"";
// put.contentEncoding = @"";
// put.contentDisposition = @"";
//put.objectMeta = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value1", @"x-oss-meta-name1", nil];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 可以在上传时设置元信息或者其他HTTP头部
    OSSTask * putTask = [self.client putObject:put];
    [putTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"upload object success!");
        } else {
            NSLog(@"upload object failed, error: %@" , task.error);
        }
        return nil;
    }];
});

2、遇到一些问题

2.1 名词解释
  • 关于endPoint

从AppServer请求到bucket后,看一下bucket信息,对照阿里云文档上的bucket对应的endPoint,如果是北京,endPoint是http://oss-cn-beijing.aliyuncs.com。

  • Bucket 上绑定 CNAME是什么意思

  • 构建OSSCredentialProvider时,文档上说的”应用服务器地址“是什么?

就是自己请求token的服务器地址。

  • 文件上传的bucketName是什么?

    就是文件名,它决定了你的文件上传后在阿里云的存储路径。

  • 文件上传的objectKey是什么?

    文件名称

2.2 项目问题
2.2.1 crash:[__NSDictionaryM oss_setObject:forKey:]: unrecognized selector sent to instance

OSSHttpHeaderXOSSCallback为空导致字典闪退。

解决办法:xcode的Build Setting - Other Lingk Flag 添加的 -ObjC 。

2.2.2 crash:-[OSSFederationCredentialProvider getToken:](self=<unavailable>

使用OSSFederationCredentialProvider上传闪退,使用OSSStsTokenCredentialProvider上传成功。

2.2.3 error Code=-403 Access denied by authorizer's policy

权限问题,见OSS常见403错误的描述及排查解决方法

临时用户访问无权限,该临时用户角色扮演指定授权策略,该授权策略无权限。

我的项目里是因为传送文件的时候传到Buckrt的根目录了,问了下运维说是没有权限的,只能传到xx目录下,怎么办呢?

我们不是要向AppServer请求STS授权信息吗,让接口加一个字段,把需要传到的目录给我们,我们只需在上传你的时候设置文件名为 ”文件目录/文件名“即可。

put.objectKey = @"demand/测试视频";//demand是需要将文件传到的目录,不设置的话是默认传到Bucket的根目录,很可能是没权限,报403。
2.2.4 发问:上传过程中token过期了会怎么样,怎么知道token过期

问题:
1、STS授权每次上传都需要设置一下token吗?

2、上传过程中token过期了会怎么样,会暂停上传吗?有么有什么回调?

3、怎么知道token过期,有么有什么信息回调?怎么监控token是不是过期了?

回复:
问题1、不需要,只用确保您本次上传时候用的那个STS的信息没有过期即可

问题2、如果是普通上传(putObject这种),我们只会在刚开始传第一个包的时候,验证请求的有效性,持续上传过程中,不会反复验证;所以要么是刚开始就报错了,要么是整个文件传完了。
如果是分片上传,因为每一个分片都是一个独立的http请求,所以每一次独立的http请求时候都会验证STS有效性,此时中间某个分片上传时候STS验证超时了,那么后续的上传都会失败,已经上传的文件会以碎片形式存在于OSS的碎片管理中(OSS控制台上可以看下),后续也可以利用断点方式去续传(传入新的不过期的STS即可)(断点方式:https://help.aliyun.com/document_detail/32063.html?spm=a2c4g.11186623.6.1133.2ec728129Mmuho)

问题3、OSS服务端会去校验STS的有效性,这个没有消息回调的,您再生成STS的时候,其中STS里面会返回一个ExpireTime,这个就是表示未来过了这个时间点就过期了,您可以监控当前时间与返回的这个ExpireTime的差值

2.2.5 request的callbackParam怎么用
    OSSPutObjectRequest *put = [[OSSPutObjectRequest alloc] init];
    //必填字段
    put.bucketName = self.bucketName;
    put.objectKey = [NSString stringWithFormat:@"demand/%@",fileName];
    put.uploadingFileURL = [NSURL fileURLWithPath:filePath];
    put.callbackParam = @{@"key":@"value",
                          @"key":@"value"
                          }
    //可选字段,可不设置
    put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
        if (progressBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                progressBlock(bytesSent,totalByteSent,totalBytesExpectedToSend);
            });
        }
    };

自定义字典,key value可以自己传入,上传结果的地方会返回。

2.2.6 token刷新怎么设置

我使用的是OSSStsTokenCredentialProvider来创建client对象的,当token过期后怎么刷新?直接设置到credential里面吗?

OSSStsTokenCredentialProvider *credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:accessKeyId secretKeyId:accessKeySecret securityToken:secretToken];
self.client = [[OSSClient alloc] initWithEndpoint:endPoint credentialProvider:credential];

credential对象里有这几个属性:

@property (nonatomic, copy) NSString * accessKeyId;
@property (nonatomic, copy) NSString * secretKeyId;
@property (nonatomic, copy) NSString * securityToken;

问:
问题描述 : 您好,我有以下业务问题需要咨询,希望贵方能给出意见或者建议

问题:当有效期到之前,我重新从AppServer获取最新的STS授权信息,我怎么讲最新的token设置到OSS里?

我使用的是这种方式创建的client对象:

OSSStsTokenCredentialProvider *credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:accessKeyId secretKeyId:accessKeySecret securityToken:secretToken];
self.client = [[OSSClient alloc] initWithEndpoint:endPoint credentialProvider:credential];

我看credential对象内部有这几个属性:

@property (nonatomic, copy) NSString * accessKeyId;
@property (nonatomic, copy) NSString * secretKeyId;
@property (nonatomic, copy) NSString * securityToken;

我是需要将credential保存全局的对象,刷新的时候直接set对应的accessKeyId、secretKeyId和securityToken吗?

还是有其他更加合理的做法呢?

答:
在您判断到Token即将过期时,您可以重新构造新的OSSClient,也可以通过如下方式更新CredentialProvider:
id credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:@"<StsToken.AccessKeyId>" secretKeyId:@"<StsToken.SecretKeyId>" securityToken:@"<StsToken.SecurityToken>"];
client = [[OSSClient alloc] initWithEndpoint:endpoint credentialProvider:credential];

可以参考下文档说明;https://help.aliyun.com/document_detail/32059.html?spm=a2c4g.11186623.6.1149.2e88242eCDvrmL

更新token这样处理吧,OSS SDK里面已经帮用户考虑和设计的很好了。

id<OSSCredentialProvider> credential2 = [[OSSFederationCredentialProvider alloc] initWithFederationTokenGetter:^OSSFederationToken * {
    // 构造请求访问您的业务server
    NSURL * url = [NSURL URLWithString:@"http://localhost:8080/distribute-token.json"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    OSSTaskCompletionSource * tcs = [OSSTaskCompletionSource taskCompletionSource];
    NSURLSession * session = [NSURLSession sharedSession];
    // 发送请求
    NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                    if (error) {
                                                        [tcs setError:error];
                                                        return;
                                                    }
                                                    [tcs setResult:data];
                                                }];
    [sessionTask resume];
    // 需要阻塞等待请求返回
    [tcs.task waitUntilFinished];
    // 解析结果
    if (tcs.task.error) {
        NSLog(@"get token error: %@", tcs.task.error);
        return nil;
    } else {
        // 返回数据是json格式,需要解析得到token的各个字段
        NSDictionary * object = [NSJSONSerialization JSONObjectWithData:tcs.task.result
                                                                options:kNilOptions
                                                                  error:nil];
        OSSFederationToken * token = [OSSFederationToken new];
        token.tAccessKey = [object objectForKey:@"AccessKeyId"];
        token.tSecretKey = [object objectForKey:@"AccessKeySecret"];
        token.tToken = [object objectForKey:@"SecurityToken"];
        token.expirationTimeInGMTFormat = [object objectForKey:@"Expiration"];
        NSLog(@"get token: %@", token);
        return token;
    }
}];
2.2.7 为什么建议将client对象保存全局单例中,目的是什么?

提交了工单,阿里的回复是:“为了避免对共享资源的多重占用,可以节约系统资源 ,防止内存溢出等”。

2.2.8 上传时设置 Content-Type 和开启校验 MD5的目的是什么?
2.3 注意事项

1、OSS SDK里面使用了分类,所以别忘了在项目的Other Link Flag处添加-Objc。

2、如果已经存在同名的Object,并且有访问权限,则新添加的文件将覆盖原来的文件。

3、上传文件

3.1、简单上传

上传本地文件或者内存中的文件。

先去自己服务器请求STS授权信息,然后初始化OSSClient对象,然后添加文件上传。

请求STS信息是个异步的,如果未请求到STS信息就调用了上传,肯定会报错,OSSClient的初始化需要传入STS的授权信息

OSSStsTokenCredentialProvider *credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:accessKeyId secretKeyId:accessKeySecret securityToken:secretToken];
self.client = [[OSSClient alloc] initWithEndpoint:endPoint credentialProvider:credential];

OSSStsTokenCredentialProvider这个类在iOS文档中不推荐使用,但是能用。

推荐使用

NSString *endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 由阿里云颁发的AccessKeyId/AccessKeySecret构造一个CredentialProvider。
// 推荐使用OSSAuthCredentialProvider,token过期后会自动刷新。
id<OSSCredentialProvider> credential = [[OSSAuthCredentialProvider alloc] initWithAuthServerUrl:@"应用服务器地址,例如http://abc.com:8080"];
client = [[OSSClient alloc] initWithEndpoint:endPoint credentialProvider:credential];

但是这种绝对大多数情况下都是不实用的,因为我们自己请求自己的AppServer端需要验签,传公参数等,并不是一个url就能去请求的,如果只是一个url就能请求STS的授权信息,那别人把url拿去不就可以乱往阿里云上传文件了吗。

还是阿里的考虑是我理解错误了?可能是我理解错误,我经常理解有问题。

看到后面果然是我理解的错误,阿里的这种做法真的很好,很为用户考虑。如果不明白请看2.2.6 token刷新怎么设置。

3.2、分片上传

文件较大时,可以使用分片上传

这里是有关分片上传的 对象存储OSS-分片上传

3.3、追加上传

3.4、断点续传

支持并发上传、自定义分片大小。大文件上传推荐使用断点续传。


未完成


三、可能会用到的一些方法

//获取文件大小
- (unsigned long long)getSizeWithFilePath:(nonnull NSString *)filePath error:(NSError **)error
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSDictionary *attributes = [fm attributesOfItemAtPath:filePath error:error];
    return attributes.fileSize;
}
//获取当前时间时间戳
+ (NSString *)getTimeStamp {
    NSDate *current = [NSDate date];
    NSTimeInterval timestamp = [current timeIntervalSince1970];
    return [NSString stringWithFormat:@"%ld", (NSInteger)timestamp];
}
/**
 UTC, standard ISO8601, formate YYYY-MM-DDThh:mm:ssZ

 @return UTC time string
 */
+ (NSString *)get8601UTC {

    NSDate *date = [NSDate date];

    NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"YYYY-MM-dd hh:mm:ss";
    formatter.timeZone = timeZone;

    NSString *UTCTime = [formatter stringFromDate:date];
    NSString *formateUTC = [[UTCTime stringByReplacingOccurrencesOfString:@" " withString:@"T"] stringByAppendingString:@"Z"];

    return formateUTC;
}
//判断时间是否过有效期
- (BOOL)reloadOSSTokenIfNeed
{
    NSDate * expirationDate = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)(self.federationToken.expirationTimeInMilliSecond / 1000)];
    NSTimeInterval interval = [expirationDate timeIntervalSinceDate:[NSDate oss_clockSkewFixedDate]];
    /* if this token will be expired after less than 2min, we abort it in case of when request arrived oss server,
     it's expired already. */
    if (interval < 10 * 60) {
        OSSLogDebug(@"get federation token, but after %lf second it would be expired", interval);
        return YES;
    }
    return NO;
}

//读取文件路径下的文件

- (NSData *)fileData:(NSString *)filePath {
    NSError *readError;
    NSFileHandle *fileHande = [NSFileHandle fileHandleForReadingFromURL:[NSURL URLWithString:filePath] error:&readError];
    if (!readError) {
        return [fileHande readDataToEndOfFile];
    }
    return nil;
}

//读取指定的分片文件

//每个分片的大小 文件大小/分片数
uint64_t offset = uplodFile.totalBytes / chuckCount;
NSFileHandle* readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
[readHandle seekToFileOffset:offset * (i -1)];

NSData* data = [readHandle readDataOfLength:offset];
uploadPart.uploadPartData = data;

MD5

#import <CommonCrypto/CommonDigest.h>
//MD5
+ (NSString *)md5:(NSString *)string {
    const char * original_str = [string UTF8String];
    unsigned char digist[CC_MD5_DIGEST_LENGTH];
    CC_MD5(original_str, (uint)strlen(original_str), digist);
    NSMutableString* outPutStr = [NSMutableString stringWithCapacity:10];
    for(int  i =0; i<CC_MD5_DIGEST_LENGTH;i++){
        [outPutStr appendFormat:@"%02x", digist[i]];
    }
    return [outPutStr lowercaseString];
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morris_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值