// // nasUploadManager.m // Private-X // // Created by xd h on 2024/8/7. // #import "nasUploadManager.h" #import "AFNetworking.h" #import "AFNetworkReachabilityManager.h" #import "AFHTTPSessionManager.h" #import "frpUploadModel.h" #define Kboundary @"Boundaryhxd" #define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding] @interface nasUploadManager () @property(nonatomic,strong)AFHTTPSessionManager *uploadManager; @property (nonatomic, strong) NSURLSessionDataTask *uploadTask; @end @implementation nasUploadManager + (instancetype)shareInstance { static nasUploadManager *_instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; } - (id)init { self = [super init]; if (self) { [self initManager]; } return self; } - (void)initManager { _uploadManager = [[AFHTTPSessionManager alloc] init]; _uploadManager.requestSerializer = [AFJSONRequestSerializer serializer]; _uploadManager.responseSerializer = [AFJSONResponseSerializer serializer]; [_uploadManager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; _uploadManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/json", @"text/javascript",@"text/html",@"text/plain",nil]; _uploadManager.requestSerializer.timeoutInterval = 180; } #pragma mark NSURLSessionDataDelegate // 1响应 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ completionHandler(NSURLSessionResponseAllow); } // 上传进度 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{ //每包发送的大小bytesSent,totalBytesSent已经上传了多少;totalBytesExpectedToSend总共要发送多少 // 32768 = 32KB HLog(@"didSendBodyData: %lld--%lld-%lld", bytesSent, totalBytesSent, totalBytesExpectedToSend); } // 2 接收数据 //- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ // // NSDictionary *infoDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; // HLog(@"%@", infoDict); // //} // 3 完成 //- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ // HLog(@"上传完成 task:%@---error:%@", task,error); //} - (NSMutableData *)getBodyDataWithRequest:(NSMutableURLRequest *)request withModel:(uploadFileDataModel*)dataModel withData:(NSData*)data withPara:(NSMutableDictionary*)params{ //1 边界符号要配置请求头里面去 /* multipart/form-data 是表单格式 charset=utf-8 是utf-8编码 bounary 是表单开头 */ [request setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", Kboundary] forHTTPHeaderField:@"Content-Type"]; /// body NSMutableData *boydData = [NSMutableData data]; // 2.1 边界符号(开始边界) //2.1.1 其它参数 NSMutableString *paraString = [NSMutableString new]; //[paraString appendFormat:@"--%@\r\n",Kboundary];//\n:换行 \n:切换到行首 for (NSString *key in params) { NSString *value = params[key]; [paraString appendFormat:@"--%@\r\n",Kboundary];//\n:换行 \n:切换到行首 [paraString appendFormat:@"Content-Disposition: form-data; name=\"%@\"",key]; [paraString appendFormat:@"\r\n"]; [paraString appendFormat:@"\r\n"]; [paraString appendFormat:@"%@\r\n",value]; } //[boydData appendData:[paraString dataUsingEncoding:NSUTF8StringEncoding]]; // body每一个段内容以换行符作为结束标示 NSString *fileBeginBoundary = [NSString stringWithFormat:@"--%@\r\n", Kboundary]; //[boydData appendData:[fileBeginBoundary dataUsingEncoding:NSUTF8StringEncoding]]; [paraString appendString:fileBeginBoundary]; // 2.2 属性配置 名字;key;类型 NSString *serverFileKey = @"image"; //key //NSString *serverFileKey = @"file"; NSString *serverContentTypes = @"image/png"; //类型 if (dataModel.curUploadFileType == uploadFileTypeVideo) { serverFileKey = @"video"; serverContentTypes = @"video/mp4"; } NSString *serverFileName = dataModel.filename; //name // filename已命名文件; name相当于一个key, 这个名字和服务器保持一致 /* 理解key,表单发送给服务端,服务端拿到数据之后,可以将任务解析成一个字典了imageDict;图片数据会通过这个字典里面的name来获取图片(伪代码 image = imageDict[serverFileKey]) */ //2.3 拼接数据(创建一个字符串来拼装) NSMutableString *string = [NSMutableString new]; [string appendFormat:@"Content-Disposition:form-data; name=\"%@\"; filename=\"%@\" ", @"file", serverFileName]; //[string appendFormat:@"%@", KNewLine]; [string appendFormat:@"\r\n"]; [string appendFormat:@"Content-Type:%@", serverContentTypes]; // [string appendFormat:@"%@", KNewLine]; // [string appendFormat:@"%@", KNewLine]; [string appendFormat:@"\r\n"]; [string appendFormat:@"\r\n"]; // [boydData appendData:[string dataUsingEncoding:NSUTF8StringEncoding]]; [paraString appendString:string]; [boydData appendData:[paraString dataUsingEncoding:NSUTF8StringEncoding]]; // 2.3 拼接数据(拼接文件数据) [boydData appendData:data]; // 2.4 边界符号 (结束边界) NSString *fileEndBoundary = [NSString stringWithFormat:@"\r\n--%@--", Kboundary]; [boydData appendData:[fileEndBoundary dataUsingEncoding:NSUTF8StringEncoding]]; return boydData; } - (void)startUpload:(NSMutableDictionary *)params model:(uploadFileDataModel*)dataModel data:(NSData *)data success:(netWork_Success)success faild:(netWork_Faild)faildStr { NSString *urlString = ksharedAppDelegate.NASFileByBoxService; urlString = [[NSString alloc] initWithFormat:@"%@uploadFile",urlString]; NSURL *URL = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; [request setHTTPMethod:@"POST"]; //请求体 NSMutableData *bodyData = [self getBodyDataWithRequest:request withModel:dataModel withData:data withPara:params]; //设置请求体 [request setHTTPMethod:@"POST"]; [request setHTTPBody:bodyData]; //设置请求体长度 NSInteger length = [bodyData length]; [request setValue:[NSString stringWithFormat:@"%ld",length] forHTTPHeaderField:@"Content-Length"]; //设置 POST请求文件上传 [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"]; KWeakSelf //回话对象 NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; //请求task /* 第一个参数:请求对象 第二个参数:传递是要上传的数据(请求体) 第三个参数: */ NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:nil completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { //解析 [weakSelf handleCustomUploadResultBy:data withResponse:response withError:error success:success faild:faildStr]; }]; // NSString *filePath = [cachesFileManager getFilePathWithName:dataModel.filename type:uploadFileTypeVideo]; // 文件路径 // NSURL *filePathUrl = [NSURL URLWithString:filePath]; // // NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:filePathUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // HLog(@"data string:%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]); //[weakSelf handleCustomUploadResultBy:data withResponse:response withError:error]; // }]; // NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // // HLog(@"data string:%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]); //[weakSelf handleCustomUploadResultBy:data withResponse:response withError:error]; // //// NSJSONSerialization *object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; //// NSDictionary *dict = (NSDictionary *)object; //// NSLog(@"=====%@",[dict objectForKey:@"success"]); // }]; //执行Task [uploadTask resume]; self.uploadTask = uploadTask; } #pragma mark 用系统方法写的上传处理 - (void)handleCustomUploadResultBy:(NSData*)data withResponse:(NSURLResponse*)response withError:(NSError*)error success:(netWork_Success)success faild:(netWork_Faild)faildStr{ if(error){ HLog(@"上传错误:%@",error) faildStr(error); return; } NSJSONSerialization *object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; NSDictionary *dict = (NSDictionary *)object; frpUploadModel *model = [[frpUploadModel alloc] initWithDictionary:dict error:nil]; if(model && [model.msg isEqualToString:@"success"]){ success(dict); } else{ NSError *err = [NSError new]; faildStr(err); } } #pragma mark afnetwork - (void)nasUploadFileToFileServiceWithParams:(NSMutableDictionary *)params model:(uploadFileDataModel*)dataModel data:(NSData *)data success:(netWork_Success)success faild:(netWork_Faild)faildStr { //test code // [self startUpload:params model:dataModel data:data success:success faild:faildStr]; // return; NSString *urlString = ksharedAppDelegate.NASFileByBoxService; urlString = [[NSString alloc] initWithFormat:@"%@uploadFile",urlString]; NSInteger position = 0; if([[params allKeys] containsObject:@"position"]){ NSNumber *positionNumber = params[@"position"]; position = positionNumber.longValue; } __block long dataLength = [data length]; HLog(@"上传地址:%@---%@---position:%ld--data 长度:%ld ",urlString,params,position,[data length]); self.uploadTask = [_uploadManager POST:urlString parameters:params constructingBodyWithBlock:^(id _Nonnull formData) { NSString *mimeType =@"application/octet-stream"; if ([[params allKeys] containsObject:@"imageType"]) { mimeType = @"image/jpeg"; //mimeType = @"jpg"; } else if ([[params allKeys] containsObject:@"videoType"]) { //mimeType = @"video/mp4"; mimeType = @"video"; } [formData appendPartWithFileData:data name:@"file" fileName:params[@"filename"] mimeType:mimeType]; } progress:^(NSProgress * _Nonnull uploadProgress) { HLog(@"上传 Progress:--%@---%lld",uploadProgress,uploadProgress.completedUnitCount) //dataModel.didUploadBytes = position + (uploadProgress.completedUnitCount); long didSendData = (long)(uploadProgress.fractionCompleted *dataLength); if(uploadProgress.fractionCompleted==1){ didSendData = dataLength; } dataModel.didUploadBytes = position + didSendData; [[NSNotificationCenter defaultCenter] postNotificationName:uploadFileRefreshNotification object:dataModel]; } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { success(responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error, id _Nonnull responseObject) { if(error.code != -999){ faildStr(error); } }]; data = nil; } - (void)beginUploadDataBy:(uploadFileDataModel*)dataModel success:(netWork_Success)success faild:(netWork_Faild)faildStr { NSMutableDictionary *paraDict = [NSMutableDictionary new]; NSString* taskUid = dataModel.taskId; if(!taskUid || taskUid.length == 0){ taskUid = [iTools getTaskUidStr]; } [paraDict setObject:taskUid forKey:@"taskId"]; [paraDict setObject:@0 forKey:@"position"]; [paraDict setObject:@"true" forKey:@"isLast"]; if(dataModel.savePath){ [paraDict setObject:dataModel.savePath forKey:@"savePath"]; } else{ HLog(@"获取保存路径失败") return; } if(dataModel.filename){ [paraDict setObject:dataModel.filename forKey:@"filename"]; } else{ HLog(@"获取用户名失败") return; } KWeakSelf if(dataModel.curUploadFileType == uploadFileTypeImage){ [paraDict setObject:@1 forKey:@"imageType"]; NSData *curData = dataModel.imageData; [self nasUploadFileToFileServiceWithParams:paraDict model:dataModel data:curData success:^(id _Nonnull responseObject) { HLog(@"%@上传完成 000",dataModel.filename) success(responseObject); } faild:^(NSError * _Nonnull error) { HLog(@"%@上传失败",dataModel.filename) faildStr(error); }]; } else{ [paraDict setObject:@0 forKey:@"videoType"]; [paraDict setObject:@"false" forKey:@"isLast"]; long curPosition = dataModel.didUploadBytes; [self beginUploadVideoDataFunBy:dataModel with:curPosition withPara:paraDict success:^(id _Nonnull responseObject) { success(responseObject); } faild:^(NSError * _Nonnull error) { faildStr(error); }]; } } - (void)beginUploadVideoDataFunBy:(uploadFileDataModel*)dataModel with:(NSInteger)position withPara:(NSMutableDictionary*)paraDict success:(netWork_Success)success faild:(netWork_Faild)faildStr { BOOL isLastPicece = NO; if((dataModel.totalBytes - position) <= MaxNasUploadPieceSzie){ [paraDict setObject:@"true" forKey:@"isLast"]; isLastPicece = YES; } [paraDict setObject:[NSNumber numberWithLong:position] forKey:@"position"]; //视频数据切片 __block NSData *videoData = [self cutVideoFileFunAtIndex:position withMaxLenght:MaxNasUploadPieceSzie withModel:dataModel]; KWeakSelf [self nasUploadFileToFileServiceWithParams:paraDict model:dataModel data:videoData success:^(id _Nonnull responseObject) { HLog(@"%@上传完成 222 %@",dataModel.filename,responseObject) videoData = nil; frpUploadModel *model = [[frpUploadModel alloc] initWithDictionary:responseObject error:nil]; if(model && model.msg){ if (isLastPicece) { success(responseObject); } else{ [weakSelf beginUploadVideoDataFunBy:dataModel with:model.position withPara:paraDict success:^(id _Nonnull responseObject) { success(responseObject); } faild:^(NSError * _Nonnull error) { NSError *err = error; if(error.code != -999){ faildStr(err); } }]; } } else{ videoData = nil; NSError *err = [NSError new]; faildStr(err); } } faild:^(NSError * _Nonnull error) { HLog(@"%@上传失败---%@",dataModel.filename,error) videoData = nil; faildStr(error); }]; } #pragma mark 分段读视频文件 -(NSData*)cutVideoFileFunAtIndex:(NSUInteger)dataIndex withMaxLenght:(NSInteger)maxLengt withModel:(uploadFileDataModel*)dataModel{ NSString *filePath = [cachesFileManager getFilePathWithName:dataModel.filename type:uploadFileTypeVideo]; // 文件路径 NSFileManager *manager0 = [NSFileManager defaultManager]; if(![manager0 fileExistsAtPath:filePath]) { return [NSData new]; } NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; // 创建文件句柄 // 设置分段读取的大小,这里以每次读取1KB为例 const NSUInteger chunkSize = maxLengt;//cutVideoPieceSzie;//5 * 1024 *1024; NSMutableData *data = [NSMutableData data]; //把视频切成两份文件给凌云 // NSData* chunk = [fileHandle readDataOfLength:MaxNasUploadPieceSzie]; // // [cachesFileManager getFileNameWithContent:chunk fileName:[[NSString alloc] initWithFormat:@"1_%@",dataModel.filename] type:uploadFileTypeVideo]; // // [fileHandle seekToFileOffset:MaxNasUploadPieceSzie]; // NSData* chunk2 = [fileHandle readDataToEndOfFile]; // [cachesFileManager getFileNameWithContent:chunk2 fileName:[[NSString alloc] initWithFormat:@"2_%@",dataModel.filename] type:uploadFileTypeVideo]; // // return chunk2; if (fileHandle) { long long endOfFile = [fileHandle seekToEndOfFile]; if(dataModel.totalBytes == 0 || dataModel.totalBytes < endOfFile){//异常处理 dataModel.totalBytes = endOfFile; } //异常处理 if(endOfFile == dataIndex){ dataModel.totalBytes = endOfFile; dataModel.didUploadBytes = endOfFile; dataModel.curUploadStateType = uploadStateDone; [fileHandle closeFile]; return data; } if (endOfFile >= chunkSize) { // 读取文件的分段数据到某个位置 [fileHandle seekToFileOffset:dataIndex]; // 读取文件的分段数据 NSData* chunk = [fileHandle readDataOfLength:chunkSize]; if (chunk) { [data appendData:chunk]; } } else{ // 读取文件的分段数据到某个位置 [fileHandle seekToFileOffset:dataIndex]; [data appendData:[fileHandle readDataToEndOfFile]]; } // 在这里可以对文件内容进行处理 // ... // 关闭文件句柄 [fileHandle closeFile]; } HLog(@"视频切片完成 dataIndex:%ld --长度:%ld",dataIndex,[data length]) return data; } #pragma mark 取消任务 - (void)cancelUploadTaskFun { [self.uploadTask cancel]; } @end