383 lines
17 KiB
Objective-C
383 lines
17 KiB
Objective-C
//
|
||
// UploadFile.m
|
||
// xplan-ios
|
||
//
|
||
// Created by GreenLand on 2022/2/23.
|
||
//
|
||
#import <QCloudCOSXML/QCloudCOSXML.h>
|
||
#import "UploadFile.h"
|
||
#import <AFNetworking.h>
|
||
#import "Api+Mine.h"
|
||
#import "UploadFileModel.h"
|
||
static UploadFile* manager;
|
||
|
||
@interface UploadFile()<QCloudSignatureProvider,QCloudCredentailFenceQueueDelegate>
|
||
// 一个脚手架实例
|
||
@property (nonatomic) QCloudCredentailFenceQueue* credentialFenceQueue;
|
||
@property(nonatomic,strong) UploadFileModel *fileModel;
|
||
|
||
// MARK: 批量下载部分, 后续要新建一个 object 来承载业务
|
||
@property (nonatomic, strong) AFHTTPSessionManager *manager;
|
||
@property (nonatomic, strong) NSData *resumeData;
|
||
@property (nonatomic, strong) NSMutableArray<NSURLSessionDownloadTask *> *tasks;
|
||
|
||
@end
|
||
|
||
@implementation UploadFile
|
||
|
||
+ (instancetype)share {
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
manager = [[UploadFile alloc] init];
|
||
});
|
||
return manager;
|
||
}
|
||
|
||
-(void)initQCloud{
|
||
[Api getQCloudInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||
if (code == 200){
|
||
UploadFileModel *fileModel = [UploadFileModel modelWithDictionary:data.data];
|
||
self.fileModel = fileModel;
|
||
QCloudServiceConfiguration* configuration = [QCloudServiceConfiguration new];
|
||
configuration.appID = fileModel.appId;
|
||
QCloudCOSXMLEndPoint* endpoint = [[QCloudCOSXMLEndPoint alloc] init];
|
||
endpoint.regionName = fileModel.region;
|
||
// 使用 HTTPS
|
||
endpoint.useHTTPS = YES;
|
||
configuration.endpoint = endpoint;
|
||
// 密钥提供者为自己
|
||
configuration.signatureProvider = self;
|
||
// 初始化 COS 服务示例
|
||
[QCloudCOSXMLService registerDefaultCOSXMLWithConfiguration:configuration];
|
||
[QCloudCOSTransferMangerService registerDefaultCOSTransferMangerWithConfiguration:
|
||
configuration];
|
||
|
||
self.credentialFenceQueue = [QCloudCredentailFenceQueue new];
|
||
self.credentialFenceQueue.delegate = self;
|
||
|
||
}
|
||
}];
|
||
}
|
||
#pragma mark- QCloudSignatureProvider
|
||
- (void) signatureWithFields:(QCloudSignatureFields*)fileds
|
||
request:(QCloudBizHTTPRequest*)request
|
||
urlRequest:(NSMutableURLRequest*)urlRequst
|
||
compelete:(QCloudHTTPAuthentationContinueBlock)continueBlock
|
||
{
|
||
|
||
[Api getQCloudInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||
if (code == 200){
|
||
UploadFileModel *fileModel = [UploadFileModel modelWithDictionary:data.data];
|
||
QCloudCredential* credential = [QCloudCredential new];
|
||
|
||
|
||
// 临时密钥 SecretId
|
||
|
||
credential.secretID = fileModel.secretId;
|
||
// 临时密钥 SecretKey
|
||
|
||
credential.secretKey = fileModel.secretKey;
|
||
// 临时密钥 Token
|
||
|
||
credential.token = fileModel.sessionToken;
|
||
/** 强烈建议返回服务器时间作为签名的开始时间, 用来避免由于用户手机本地时间偏差过大导致的签名不正确(参数startTime和expiredTime单位为秒)
|
||
*/
|
||
credential.startDate = [NSDate dateWithTimeIntervalSince1970:fileModel.startTime]; // 单位是秒
|
||
credential.expirationDate = [NSDate dateWithTimeIntervalSince1970:fileModel.expireTime];// 单位是秒
|
||
|
||
|
||
QCloudAuthentationV5Creator* creator = [[QCloudAuthentationV5Creator alloc]
|
||
initWithCredential:credential];
|
||
// 注意 这里不要对urlRequst 进行copy以及mutableCopy操作
|
||
QCloudSignature *signature = [creator signatureForData:urlRequst];
|
||
continueBlock(signature, nil);
|
||
|
||
}
|
||
}
|
||
];
|
||
|
||
}
|
||
|
||
#pragma mark - QCloudCredentailFenceQueueDelegate
|
||
- (void) fenceQueue:(QCloudCredentailFenceQueue * )queue requestCreatorWithContinue:(QCloudCredentailFenceQueueContinue)continueBlock
|
||
{
|
||
//这里同步从◊后台服务器获取临时密钥,强烈建议将获取临时密钥的逻辑放在这里,最大程度上保证密钥的可用性
|
||
//...
|
||
|
||
[Api getQCloudInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||
if (code == 200){
|
||
UploadFileModel *fileModel = [UploadFileModel modelWithDictionary:data.data];
|
||
QCloudCredential* credential = [QCloudCredential new];
|
||
// 临时密钥 SecretId
|
||
credential.secretID = fileModel.secretId;
|
||
// 临时密钥 SecretKey
|
||
|
||
credential.secretKey = fileModel.secretKey;
|
||
// 临时密钥 Token
|
||
|
||
credential.token = fileModel.sessionToken;
|
||
/** 强烈建议返回服务器时间作为签名的开始时间, 用来避免由于用户手机本地时间偏差过大导致的签名不正确(参数startTime和expiredTime单位为秒)
|
||
*/
|
||
credential.startDate = [NSDate dateWithTimeIntervalSince1970:fileModel.startTime]; // 单位是秒
|
||
credential.expirationDate = [NSDate dateWithTimeIntervalSince1970:fileModel.expireTime];// 单位是秒
|
||
QCloudAuthentationV5Creator* creator = [[QCloudAuthentationV5Creator alloc]
|
||
initWithCredential:credential];
|
||
continueBlock(creator, nil);
|
||
}
|
||
}];
|
||
}
|
||
/// 上传一个文件
|
||
/// @param filePath 文件地址
|
||
/// @param fileName 文件的名字
|
||
/// @param success 成功
|
||
/// @param failure 失败
|
||
- (void)QCloudUploadFile:(NSString *)filePath
|
||
named:(NSString *)fileName
|
||
success:(void (^)(NSString *key, NSDictionary *resp))success
|
||
failure:(void (^)(NSNumber *resCode, NSString *message))failure {
|
||
|
||
|
||
QCloudCOSXMLUploadObjectRequest* put = [QCloudCOSXMLUploadObjectRequest new];
|
||
// 本地文件路径
|
||
NSURL* url = [NSURL fileURLWithPath:filePath];
|
||
// 存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.cloud.tencent.com/cos5/bucket
|
||
put.bucket = self.fileModel.bucket;
|
||
// 对象键,是对象在 COS 上的完整路径,如果带目录的话,格式为 "video/xxx/movie.mp4"
|
||
put.object = fileName;
|
||
//需要上传的对象内容。可以传入NSData*或者NSURL*类型的变量
|
||
put.body = url;
|
||
//监听上传进度
|
||
[put setSendProcessBlock:^(int64_t bytesSent,
|
||
int64_t totalBytesSent,
|
||
int64_t totalBytesExpectedToSend) {
|
||
// bytesSent 本次要发送的字节数(一个大文件可能要分多次发送)
|
||
// totalBytesSent 已发送的字节数
|
||
// totalBytesExpectedToSend 本次上传要发送的总字节数(即一个文件大小)
|
||
}];
|
||
|
||
//监听上传结果
|
||
[put setFinishBlock:^(id outputObject, NSError *error) {
|
||
//可以从 outputObject 中获取 response 中 etag 或者自定义头部等信息
|
||
if (error) {
|
||
failure(@(error.code),error.localizedDescription);
|
||
return;
|
||
}
|
||
QCloudUploadObjectResult * result = (QCloudUploadObjectResult *)outputObject;
|
||
NSArray *urlList = [result.location componentsSeparatedByString:@".com/"];
|
||
if (urlList.count == 2){
|
||
NSString *url = [NSString stringWithFormat:@"%@/%@",self.fileModel.customDomain,urlList[1]];
|
||
success(url,nil);
|
||
return;
|
||
}
|
||
success(result.location,nil);
|
||
}];
|
||
|
||
[[QCloudCOSTransferMangerService defaultCOSTransferManager] UploadObject:put];
|
||
|
||
}
|
||
|
||
/// 上传一个Image
|
||
/// @param image 图片
|
||
/// @param imageName 图片的名字
|
||
|
||
/// @param success 成功
|
||
/// @param failure 失败
|
||
- (void)QCloudUploadImage:(NSData *)data
|
||
named:(NSString *)name
|
||
success:(void (^)(NSString *key, NSDictionary *resp))success
|
||
failure:(void (^)(NSNumber *resCode, NSString *message))failure{
|
||
|
||
QCloudCOSXMLUploadObjectRequest* put = [QCloudCOSXMLUploadObjectRequest new];
|
||
put.bucket = self.fileModel.bucket;
|
||
// 对象键,是对象在 COS 上的完整路径,如果带目录的话,格式为 "video/xxx/movie.mp4"
|
||
put.object = name;
|
||
//需要上传的对象内容。可以传入NSData*或者NSURL*类型的变量
|
||
put.body = data;
|
||
//监听上传进度
|
||
[put setSendProcessBlock:^(int64_t bytesSent,
|
||
int64_t totalBytesSent,
|
||
int64_t totalBytesExpectedToSend) {
|
||
// bytesSent 本次要发送的字节数(一个大文件可能要分多次发送)
|
||
// totalBytesSent 已发送的字节数
|
||
// totalBytesExpectedToSend 本次上传要发送的总字节数(即一个文件大小)
|
||
}];
|
||
|
||
//监听上传结果
|
||
[put setFinishBlock:^(id outputObject, NSError *error) {
|
||
|
||
if (error) {
|
||
failure(@(error.code),error.localizedDescription);
|
||
return;
|
||
}
|
||
QCloudUploadObjectResult * result = (QCloudUploadObjectResult *)outputObject;
|
||
NSArray *urlList = [result.location componentsSeparatedByString:@".com/"];
|
||
if (urlList.count == 2){
|
||
NSString *url = [NSString stringWithFormat:@"%@/%@",self.fileModel.customDomain,urlList[1]];
|
||
success(url,nil);
|
||
return;
|
||
}
|
||
success(result.location,nil);
|
||
}];
|
||
|
||
[[QCloudCOSTransferMangerService defaultCOSTransferManager] UploadObject:put];
|
||
}
|
||
|
||
-(void)downloadAnimationFileName:(NSString *)fileName localPath:(NSString *)localPath completion:(void (^) (BOOL isSuccess, NSString *editAudioPath))completion{
|
||
QCloudCOSXMLDownloadObjectRequest * request = [QCloudCOSXMLDownloadObjectRequest new];
|
||
|
||
|
||
// 存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.cloud.tencent.com/cos5/bucket
|
||
request.bucket = self.fileModel.bucket;
|
||
|
||
NSArray *urlList = [fileName componentsSeparatedByString:@".com/"];
|
||
|
||
// 对象键,是对象在 COS 上的完整路径,如果带目录的话,格式为 "video/xxx/movie.mp4"
|
||
request.object = [urlList safeObjectAtIndex1:1];
|
||
|
||
|
||
// 设置下载的路径 URL,如果设置了,文件将会被下载到指定路径中
|
||
request.downloadingURL = [NSURL fileURLWithPath:localPath];
|
||
|
||
|
||
|
||
|
||
// 监听下载结果
|
||
[request setFinishBlock:^(id outputObject, NSError *error) {
|
||
// outputObject 包含所有的响应 http 头部
|
||
NSDictionary* info = (NSDictionary *) outputObject;
|
||
NSLog(@"%@",info);
|
||
}];
|
||
|
||
|
||
// 监听下载进度
|
||
[request setDownProcessBlock:^(int64_t bytesDownload,
|
||
int64_t totalBytesDownload,
|
||
int64_t totalBytesExpectedToDownload) {
|
||
|
||
|
||
// bytesDownload 新增字节数
|
||
// totalBytesDownload 本次下载接收的总字节数
|
||
// totalBytesExpectedToDownload 本次下载的目标字节数
|
||
}];
|
||
|
||
|
||
[[QCloudCOSTransferMangerService defaultCOSTransferManager] DownloadObject:request];
|
||
|
||
}
|
||
+(void)downloadAudioWithFileName:(NSString *)fileName
|
||
musicUrl:(NSString *)musicUrl
|
||
mainFileName:(NSString *)mainFileName
|
||
completion:(void (^) (BOOL isSuccess, NSString *editAudioPath))completion {
|
||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||
NSURL *url = [NSURL URLWithString:[musicUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
|
||
NSURLRequest *request = [NSURLRequest requestWithURL :url];
|
||
NSURLSessionDownloadTask *download = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
|
||
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
|
||
NSString *filePath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) safeObjectAtIndex1:0] stringByAppendingPathComponent:mainFileName] stringByAppendingPathComponent:fileName];
|
||
return [NSURL fileURLWithPath:filePath];
|
||
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
|
||
if (!error) {
|
||
completion(YES, filePath.path);
|
||
} else {
|
||
completion(NO, nil);
|
||
}
|
||
}];
|
||
[download resume];
|
||
}
|
||
|
||
// 批量下载
|
||
- (void)startBatchDownloadWithURLs:(NSArray<NSString *> *)URLs {
|
||
if (_manager == nil) {
|
||
// _manager = [AFHTTPSessionManager manager];
|
||
|
||
// MARK: 切换线程的思路正确,那么,整个 APP 那么多 API 没有使用异步。。。。
|
||
// 失败数量第一次就是对的
|
||
// 周一再测试是否外部异步,内部指定并发就可以
|
||
// 没有指定并发数,可以下载到一半
|
||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.example.app.background"];
|
||
_manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
|
||
_manager.operationQueue.maxConcurrentOperationCount = 10;
|
||
_tasks = [NSMutableArray array];
|
||
}
|
||
|
||
if (URLs.count == 0) {
|
||
return;
|
||
}
|
||
|
||
NSMutableArray<NSError *> *errors = [NSMutableArray array];
|
||
NSMutableArray<NSURL *> *filePaths = [NSMutableArray array];
|
||
|
||
NSInteger count = 0;
|
||
|
||
for (NSString *urlStr in URLs) {
|
||
NSURL *url = [NSURL URLWithString:urlStr];
|
||
NSURLRequest *request = [NSURLRequest requestWithURL:url];
|
||
|
||
// 获取目标文件路径
|
||
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
|
||
NSURL *destinationURL = [documentsDirectoryURL URLByAppendingPathComponent:[url lastPathComponent]];
|
||
|
||
// 检查文件是否已经存在
|
||
if ([[NSFileManager defaultManager] fileExistsAtPath:[destinationURL path]]) {
|
||
NSLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 文件已存在,跳过下载: %@", destinationURL);
|
||
[filePaths addObject:destinationURL];
|
||
continue;
|
||
}
|
||
|
||
NSURLSessionDownloadTask *downloadTask = [self.manager downloadTaskWithRequest:request
|
||
progress:nil
|
||
destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
|
||
// 下载目标位置
|
||
return destinationURL;
|
||
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
|
||
if (error) {
|
||
[errors addObject:error];
|
||
NSLog("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 下载失败:%@, %@", error, [NSDate date]);
|
||
} else {
|
||
[filePaths addObject:filePath];
|
||
NSLog("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 下载成功:%@, 时间:%@", filePath, [NSDate date]);
|
||
}
|
||
if (filePaths.count + errors.count + count == URLs.count) {
|
||
// 全部任务处理完成
|
||
NSLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 全部完成:%ld, %@", (unsigned long)URLs.count, [NSDate date]);
|
||
}
|
||
}];
|
||
|
||
[self.tasks addObject:downloadTask];
|
||
}
|
||
|
||
for (NSURLSessionDownloadTask *task in self.tasks) {
|
||
[task resume];
|
||
}
|
||
}
|
||
|
||
- (void)pasuBatchDownload {
|
||
NSLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 暂停下载");
|
||
for (NSURLSessionDownloadTask *task in self.tasks) {
|
||
[task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
|
||
if (resumeData) {
|
||
self.resumeData = resumeData;
|
||
}
|
||
}];
|
||
}
|
||
}
|
||
|
||
- (void)resumeBatchDownload {
|
||
if (self.resumeData) {
|
||
NSURLSessionDownloadTask *downloadTask = [self.manager downloadTaskWithResumeData:self.resumeData progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
|
||
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
|
||
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
|
||
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
|
||
if (error) {
|
||
NSLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 续传下载失败: %@", error.localizedDescription);
|
||
} else {
|
||
NSLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 续传下载文件保存到: %@", filePath);
|
||
}
|
||
}];
|
||
|
||
[downloadTask resume];
|
||
}
|
||
}
|
||
|
||
@end
|