上传管理
上传列表
网络变化处理
1. 网络连接中断
暂停全部上传任务
2. 网络恢复
恢复所有上传任务
3. 网络变成4G的处理
- 仅wifi上传
只有在wifi环境下才能上传文件
- 允许一次
每一次都弹框需要用户确认
- 支持4G上传
只要有网就能上传
- 手动全部恢复
普通的文件上传
需要针对当前的网络请求任务NSURLSessionTask,做暂停恢复、取消处理
大文件分块上传
除了对当前任务NSURLSessionTask做处理外,还需要对当前上传任务的队列进行处理
关于网络变化的处理,主要是对4G情况的处理,代码如下:
/**
处理上传时网络为4G的情况
@param allowBlock 允许上传的回调
*/
- (void)p_handleWWANNetWorkWithAlowBlock:(void(^)(void))allowBlock {
//从UserDefault获取,用户上次的选择
NSString *upLoadNetWorkType = [[NSUserDefaults standardUserDefaults] objectForKey:@"FileTransferUpLoad_WANNetWork"];
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"已为您加入上传列表" message:@"您在使用数据网络上传,可能会产生超额流量费用" preferredStyle:UIAlertControllerStyleActionSheet];
if ([upLoadNetWorkType isEqualToString:@"1"]) {
allowBlock();
return;
} else if ([upLoadNetWorkType isEqualToString:@"2"]) {
//调用之后清空设置,下次重新选择而不是设置为仅wifi
allowBlock();
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"HTMI_FileTransferUpLoad_WANNetWork"];
return;
} else if ([upLoadNetWorkType isEqualToString:@"3"]) {
//不继续调用,等待网络为wifi
return;
} else {
//没有设置过,需要进行提示设置
UIAlertAction *useCamera = [UIAlertAction actionWithTitle:@"总是允许数据网络上传" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"HTMI_FileTransferUpLoad_WANNetWork"];
allowBlock();
}];
UIAlertAction *desAction = [UIAlertAction actionWithTitle:@"本次允许数据网络上传" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[NSUserDefaults standardUserDefaults] setObject:@"2" forKey:@"HTMI_FileTransferUpLoad_WANNetWork"];
allowBlock();
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"HTMI_FileTransferUpLoad_WANNetWork"];
}];
UIAlertAction *usePhoto = [UIAlertAction actionWithTitle:@"仅wifi上传" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[NSUserDefaults standardUserDefaults] setObject:@"3" forKey:@"HTMI_FileTransferUpLoad_WANNetWork"];
}];
[alertController addAction:useCamera];
[alertController addAction:desAction];
[alertController addAction:usePhoto];
[self presentViewController:alertController animated:YES completion:nil];
}
}
上传完成列表
支持文件名称搜索
• 左滑删除
• 从本地库中删除记录、从列表中删除数据
• 清除本地的文件缓存
列表支持跳转到缓存管理页面
• 自由选择列表页面
• 自由选择进行删除
• 删除同单个删除处理逻辑一致
文件上传(重点)
文件上传的基础方法是基于AFN实现的,代码如下:
/**
AFN 文件上传方法
@param url 文件上传接口地址
@param baseurl 基础url
@param params 参数
@param headers 请求头参数
@param filedata 文件数据
@param name 服务器字段名
@param filename 文件名
@param mimeType 请求的类型(image/jpeg)
@param progress 传输进度
@param success 成功回调
@param fail 失败回调
@return 上传任务
*/
- (NSURLSessionDataTask *)uploadWithURL:(NSString *)url
baseURL:(NSString *)baseurl
params:(NSDictionary *)params
headers:(NSDictionary *)headers
fileData:(NSData *)filedata
name:(NSString *)name
fileName:(NSString *)filename
mimeType:(NSString *)mimeType
progress:(HTMIFileUploadProgress)progress
success:(HTMIFileUploadResponseSuccess)success
fail:(HTMIFileUploadResponseFail)fail {
AFHTTPSessionManager *manager = [self managerWithBaseURL:baseurl sessionConfiguration:YES];
//设置header
if (nil != headers) {
for (id key in headers) {
[manager.requestSerializer setValue:[headers objectForKey:key] forHTTPHeaderField:key];
}
}
NSURLSessionDataTask *urlSessionDataTask = [manager POST:url parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:filedata name:name fileName:filename mimeType:mimeType];
} progress:^(NSProgress * _Nonnull uploadProgress) {
progress(uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
id dic = [self responseConfiguration:responseObject];
success(task,dic);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
fail(task,error);
}];
return urlSessionDataTask;
}
大文件和普通文件是通过文件大小自动判断的,
封装了两个不同的类,用来做普通文件上传的:HTMINormalFileUpLoader和分块上传的HTMIFileBlockUpLoader,因为希望对外提供一个统一的调用方法,不用区分是哪种上传方式,采用了模板设计模式,两个类都实现了以下协议
#import <Foundation/Foundation.h>
#import "HTMIFileUpLoadHeader.h"
@class HTMIFileInfo;
@protocol HTMIFileUpLoader<NSObject>
/**
文件上传接口
@param fileInfo 文件信息模型
@param successBlock 上传成功回调
@param failureBlock 上传失败回调
@param progressBlock 上传进度回调
*/
- (void)uploadWithFileInfo:(HTMIFileInfo *)fileInfo
success:(UpLoaderSuccessBlock)successBlock
failure:(UpLoaderFailureBlock)failureBlock
progress:(UpLoaderProgressBlock)progressBlock;
/**
暂停
*/
- (void)pause;
/**
继续
*/
- (void)resume;
/**
取消上传
*/
- (void)cancel;
/**
是否已经暂停
@return 暂停状态
*/
- (BOOL)isPause;
@end
- 普通文件上传(HTMINormalFileUpLoader)
• 调用普通文件的上传接口异步上传 - 大文件分片上传(HTMIFileBlockUpLoader)
第一步、调用接口获取批次号
获取成功后更新本地数据库文件上传表中的文件批次号
//批次号获取成功
NSString * resultString = api.response.finialResult;
if (resultString && resultString.length > 0) {
dispatch_async(dispatch_get_main_queue(), ^(){
if (self.progressBlock != nil) {
self.progressBlock(0, self.fileSize);
}
});
@weakify(self);
dispatch_async(self.upLoaderQueue, ^(){
@strongify(self);
if (self.cancelled) {//操作是否已经取消
[self p_handleCanceled];
} else {
//1、设置batchId
self.fileInfo.batchId = resultString;
//2、插入数据库 默认已经插入了数据库
[HTMIFileSqlManager updateFileStatusWithTransferFileId:self.fileInfo.transferFileId batchId:self.fileInfo.batchId];
[self.fileInfo.taskInfoArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
HTMITaskInfo * taskInfo = (HTMITaskInfo *)obj;
taskInfo.batchId = resultString;
taskInfo.transferFileId = self.fileInfo.transferFileId;
// 插入task表
[HTMIFileSqlManager insertATaskInfoDataWithTask:taskInfo];
}];
//3、上传文件
[self p_beginUpload];
}
});
}
第二步、切割文件,拆分多任务进行异步上传
切割文件的方法如下:
/**
预处理获取文件信息,文件分块记录
@param fileInfo 文件信息
@return 文件信息模型
*/
- (HTMIFileInfo *)p_updateFileInfo:(HTMIFileInfo *)fileInfo {
//路径一定要存在
if (fileInfo.filePath.length > 0) {
if ([[NSFileManager defaultManager] fileExistsAtPath:fileInfo.filePath]) {
self.fileSize = [[UpApiUtils lengthOfFileAtPath:fileInfo.filePath] intValue];
NSInteger blockCount = self.fileSize / self.blcokSize;
NSInteger blockRemainder = self.fileSize % self.blcokSize;
if (blockRemainder > 0) {
blockCount = blockCount + 1;
}
//更新文件信息模型
fileInfo.fileLength = self.fileSize;
fileInfo.skipSize = self.blcokSize;
NSMutableArray *taskInfoArray = [[NSMutableArray alloc] init];//分块信息
for (UInt32 i = 0; i < blockCount; i++) {
@autoreleasepool {
long loc = i * self.blcokSize;
long len = self.blcokSize;
if (i == blockCount - 1) {
len = (UInt32)self.fileSize - loc;
}
NSString * fileName = [NSString stringWithFormat:@"%@_%d",fileInfo.fileName,(unsigned int)i];
HTMITaskInfo * taskInfo = [[HTMITaskInfo alloc] initWithFileName:fileName taskNo:i len:len startPos:loc endPos:(loc + len)];
[taskInfoArray addObject:taskInfo];
}
}
fileInfo.taskInfoArr = taskInfoArray;
}
}
return fileInfo;
}
写入文件上传任务表
第三步、块文件调用上传文件
更新文件上传的状态
将文件块上传任务放入队列中,开始上传
/**
上传方法
*/
- (void)p_beginUpload {
//生成队列
[self.fileInfo.taskInfoArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
HTMITaskInfo * taskInfo = (HTMITaskInfo *)obj;
if (taskInfo.status != 2) {
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//taskInfo
[self p_uploadFileBlockWithTaskInfo:taskInfo];
}];
[self.operationQueue addOperation:blockOperation];
} else {
//已经完成的
NSLog(@"taskInfo.status:%d",taskInfo.status);
}
}];
}
通过信号量控制单个块文件的上传是同步的
支持哪些操作
• 暂停单个文件
• 暂停全部文件
• 恢复单个文件
• 恢复全部文件
注意:断网后无法恢复当前正在上传的任务,需要重新创建任务, 添加到队列中
FMDB封装的使用
定义一个集成自FMDatabase的类,重写下面的方法实现数据库加密
- (BOOL)open {
if (_db) {
return YES;
}
int err = sqlite3_open([self sqlitePath], &_db );
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
} else {
//数据库open后设置加密key
[self setKey:encryptKey_];
}
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
}
注意:FMDB本文使用的版本为 'FMDB/SQLCipher', '2.5',其他版本如何使用加密方式有知道的还请不吝赐教