Files
peko-ios/YuMi/Tools/File/UploadFile.m
2024-05-20 14:34:28 +08:00

383 lines
17 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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