大文件分片断点上传显示进度条
来源:互联网 发布:linux lnmp一键安装 编辑:程序博客网 时间:2024/05/17 01:40
项目里面刚做完文件这块,本人负责文件分片上传功能模块,现将实现思路以及关键实现点整理一下。
文件分片断点上传思路如下:
第一步:分片上传
首先在本地沙盒Cache目录下创建一个专门存放上传文件的缓存文件夹Upload,检查Upload中是否有该文件(通过文件md5值+文件名找该路径),没有表示不是断点上传,在Upload中创建一个存放当前要上传文件的文件夹(命名规则为整个文件md5值+文件名),通过文件分片类获取该文件所有分片并写到分片文件夹中,分片命名跟其序列一致;有则表示是断点续传,把分片文件夹中所有分片用数组装着调用分片上传接口,在成功的回调里面删除该分片,直到所有分片上传完删除该文件夹。
第二部:合并上传到云端
这里两步合并(上传到服务器)、上传到云端均是调用接口不做赘述
核心部分分片上传代码:
在这里实现分片断点上传我用到了CKGFileUploadManager(文件上传管理单例)、CKGFileUploadCachedManager(文件上传缓存管理单例)、FileStreamOperation(文件操作流类,文件分片成分片数组)、CKGFileUploadRequest(抽出来的上传类,基于ASI)
FileStreamOperation(文件操作流类,文件分片成分片数组)核心代码(这个类是在网上找的资料):
.h
#import <Foundation/Foundation.h>#define FileFragmentMaxSize 1024 *1024 // 1MB@class FileFragment;/** * 文件流操作类 */@interface FileStreamOperation : NSObject<NSCoding>@property (nonatomic, readonly, copy) NSString *fileName;//包括文件后缀名的文件名@property (nonatomic, readonly, assign) NSUInteger fileSize;//文件大小@property (nonatomic, readonly, copy) NSString *filePath;//文件所在的文件目录@property (nonatomic, readonly, strong) NSArray<FileFragment*> *fileFragments;//文件分片数组//若为读取文件数据,打开一个已存在的文件。//若为写入文件数据,如果文件不存在,会创建的新的空文件。(创建FileStreamer对象就可以直接使用fragments(分片数组)属性)- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation ;//获取当前偏移量- (NSUInteger)offsetInFile;//设置偏移量, 仅对读取设置- (void)seekToFileOffset:(NSUInteger)offset;//将偏移量定位到文件的末尾- (NSUInteger)seekToEndOfFile;//关闭文件- (void)closeFile;#pragma mark - 读操作//通过分片信息读取对应的片数据- (NSData*)readDateOfFragment:(FileFragment*)fragment;//从当前文件偏移量开始- (NSData*)readDataOfLength:(NSUInteger)bytes;//从当前文件偏移量开始- (NSData*)readDataToEndOfFile;#pragma mark - 写操作//写入文件数据- (void)writeData:(NSData *)data;@end//上传文件片@interface FileFragment : NSObject<NSCoding>@property (nonatomic,copy)NSString *fragmentId; //片的唯一标识@property (nonatomic,assign)NSUInteger fragmentSize; //片的大小@property (nonatomic,assign)NSUInteger fragementOffset;//片的偏移量@property (nonatomic,assign)BOOL fragmentStatus; //上传状态 YES上传成功@end.m#import "FileStreamOperation.h"#import <CommonCrypto/CommonDigest.h>#pragma mark - FileStreamOperation@interface FileStreamOperation ()@property (nonatomic, copy) NSString *fileName;@property (nonatomic, assign) NSUInteger fileSize;@property (nonatomic, copy) NSString *filePath;@property (nonatomic, strong) NSArray<FileFragment*> *fileFragments;@property (nonatomic, strong) NSFileHandle *readFileHandle;@property (nonatomic, strong) NSFileHandle *writeFileHandle;@property (nonatomic, assign) BOOL isReadOperation;@end@implementation FileStreamOperation+ (NSString *)fileKey { CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); CFStringRef cfstring = CFUUIDCreateString(kCFAllocatorDefault, uuid); const char *cStr = CFStringGetCStringPtr(cfstring,CFStringGetFastestEncoding(cfstring)); unsigned char result[16]; CC_MD5( cStr, (unsigned int)strlen(cStr), result ); CFRelease(uuid); return [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08lx", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15], (unsigned long)(arc4random() % NSUIntegerMax)];}- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:[self fileName] forKey:@"fileName"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fileSize]] forKey:@"fileSize"]; [aCoder encodeObject:[self filePath] forKey:@"filePath"]; [aCoder encodeObject:[self fileFragments] forKey:@"fileFragments"];}- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self != nil) { [self setFileName:[aDecoder decodeObjectForKey:@"fileName"]]; [self setFileSize:[[aDecoder decodeObjectForKey:@"fileSize"] unsignedIntegerValue]]; [self setFilePath:[aDecoder decodeObjectForKey:@"filePath"]]; [self setFileFragments:[aDecoder decodeObjectForKey:@"fileFragments"]]; } return self;}- (BOOL)getFileInfoAtPath:(NSString*)path { NSFileManager *fileMgr = [NSFileManager defaultManager]; if (![fileMgr fileExistsAtPath:path]) { NSLog(@"文件不存在:%@",path); return NO; } self.filePath = path; NSDictionary *attr =[fileMgr attributesOfItemAtPath:path error:nil]; self.fileSize = attr.fileSize; NSString *fileName = [path lastPathComponent]; self.fileName = fileName; return YES;}// 若为读取文件数据,打开一个已存在的文件。// 若为写入文件数据,如果文件不存在,会创建的新的空文件。- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation { if (self = [super init]) { self.isReadOperation = isReadOperation; if (_isReadOperation) { if (![self getFileInfoAtPath:path]) { return nil; } self.readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path]; [self cutFileForFragments]; } else { NSFileManager *fileMgr = [NSFileManager defaultManager]; if (![fileMgr fileExistsAtPath:path]) { [fileMgr createFileAtPath:path contents:nil attributes:nil]; } if (![self getFileInfoAtPath:path]) { return nil; } self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; } } return self;}//- (instancetype)#pragma mark - 读操作//切分文件片段- (void)cutFileForFragments { NSUInteger offset = FileFragmentMaxSize; // 块数 NSUInteger chunks = (_fileSize%offset==0)?(_fileSize/offset):(_fileSize/(offset) + 1); NSMutableArray<FileFragment *> *fragments = [[NSMutableArray alloc] initWithCapacity:0]; for (NSUInteger i = 0; i < chunks; i ++) { FileFragment *fFragment = [[FileFragment alloc] init]; fFragment.fragmentStatus = NO; fFragment.fragmentId = [[self class] fileKey]; fFragment.fragementOffset = i * offset; if (i != chunks - 1) { fFragment.fragmentSize = offset; } else { fFragment.fragmentSize = _fileSize - fFragment.fragementOffset; } [fragments addObject:fFragment]; } self.fileFragments = fragments;}//通过分片信息读取对应的片数据- (NSData*)readDateOfFragment:(FileFragment*)fragment { if (fragment) { [self seekToFileOffset:fragment.fragementOffset]; return [_readFileHandle readDataOfLength:fragment.fragmentSize]; } return nil;}- (NSData*)readDataOfLength:(NSUInteger)bytes { return [_readFileHandle readDataOfLength:bytes];}- (NSData*)readDataToEndOfFile { return [_readFileHandle readDataToEndOfFile];}#pragma mark - 写操作// 写入文件数据- (void)writeData:(NSData *)data { [_writeFileHandle writeData:data];}#pragma mark - common// 获取当前偏移量- (NSUInteger)offsetInFile{ if (_isReadOperation) { return [_readFileHandle offsetInFile]; } return [_writeFileHandle offsetInFile];}// 设置偏移量,仅对读取设置- (void)seekToFileOffset:(NSUInteger)offset { [_readFileHandle seekToFileOffset:offset];}// 将偏移量定位到文件的末尾- (NSUInteger)seekToEndOfFile{ if (_isReadOperation) { return [_readFileHandle seekToEndOfFile]; } return [_writeFileHandle seekToEndOfFile];}// 关闭文件- (void)closeFile { if (_isReadOperation) { [_readFileHandle closeFile]; } else { [_writeFileHandle closeFile]; }}@end#pragma mark - FileFragment@implementation FileFragment- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:[self fragmentId] forKey:@"fragmentId"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentSize]] forKey:@"fragmentSize"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragementOffset]] forKey:@"fragementOffset"]; [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentStatus]] forKey:@"fragmentStatus"];}- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self != nil) { [self setFragmentId:[aDecoder decodeObjectForKey:@"fragmentId"]]; [self setFragmentSize:[[aDecoder decodeObjectForKey:@"fragmentSize"] unsignedIntegerValue]]; [self setFragementOffset:[[aDecoder decodeObjectForKey:@"fragementOffset"] unsignedIntegerValue]]; [self setFragmentStatus:[[aDecoder decodeObjectForKey:@"fragmentStatus"] boolValue]]; } return self;}
CKGFileUploadRequest(抽出来的上传类,基于ASI)代码:
.h
#import <Foundation/Foundation.h>#import "CKGFileModel.h"#import "FileStreamOperation.h"#import "Message.h"//分片上传成功block回调typedef void(^FragmentsUploadFinished)(CKGFileModel *fd, NSArray *fragments);@interface CKGFileUploadRequest : NSObject@property (nonatomic, copy)FragmentsUploadFinished finishedBlock;//上传请求方法- (instancetype)initWithFragments:(NSArray *)fragments file:(CKGFileModel *)file fileStreamer:(FileStreamOperation *)fileStreamer isChatSend:(BOOL)isChatSend message:(Message *)msg;@end
.m
#import "CKGFileUploadRequest.h"#import "CKGFileUploadManager.h"#import "CKGFileUploadCachedManager.h"@implementation CKGFileUploadRequest- (instancetype)initWithFragments:(NSArray *)fragments file:(CKGFileModel *)file fileStreamer:(FileStreamOperation *)fileStreamer isChatSend:(BOOL)isChatSend message:(Message *)msg{ self = [super init]; if (self) { __block float uploadCount = 0.0; NSInteger fileFragmentCount = fragments.count; for (int i = 0 ; i<fragments.count; i++) { NSString *urlString = [BASE_URL stringByAppendingString:@"/api/upload/formFile"]; NSString *myId = [UD objectForKey:UID_KEY]; NSDictionary *postData = @{ @"uid":CheckStringValue(myId), @"act":CheckStringValue(@"upload"), @"name":CheckStringValue(file.fileName), @"ext":CheckStringValue(file.fileExt), @"size":CheckStringValue(file.size), @"trunk_count":@(fileFragmentCount), @"trunk_id":@(i+1), @"md5":CheckStringValue(file.md5Str) }; ASIFormDataRequest *__weak request = [CKGHttpBase requestWithURLString:urlString andPostDictionary:postData]; request.delegate = self; //获取对应文件分片 __block NSData *data = [fileStreamer readDateOfFragment:fragments[i]];//表单填写 [request setData:data withFileName:file.fileName andContentType:@"multipart/form-data" forKey:@"file"]; [request startAsynchronous]; [request setCompletionBlock:^{ NSString *string = request.responseString; NSDictionary *dataDic = [string objectFromJSONString];// DLog(@" %zi dataDic:%@",i+1,dataDic); if ([dataDic[@"result"] integerValue] == 1 && [dataDic[@"data"][@"file_exists"] integerValue] == 0) { uploadCount++; //分片上传成功从沙盒对应路径删除// [[CKGFileUploadCachedManager sharedInstance] removeFileByName:file.fileName fragment:i+1]; //更新进度条(这里通过block或者通知传到需要更新进度条进度的地方) DLog(@"%@ progress>>>>>>>>>>>%f",file.fileName,(float)(uploadCount/fileFragmentCount)); if (file.isAsset) { if (isChatSend) { NSDictionary *dic = @{@"file":file,@"progress":@((float)(uploadCount * 1.0/fileFragmentCount)),@"message":msg}; [NC postNotificationName:@"CHATSENDFILE" object:dic]; }else { [CKGFileUploadManager sharedInstance].upProgressBlock(file,(float)(uploadCount/fileFragmentCount),msg); } } if(uploadCount == fileFragmentCount) { //所有分片上传完毕删除对应文件// [[CKGFileUploadCachedManager sharedInstance] deleteDirctoryAtPath:[NSString stringWithFormat:@"%@%@",file.md5Str,file.fileName]]; self.finishedBlock(file,fragments); uploadCount = 0.0; } } }]; } } return self;}
CKGFileUploadManager(文件上传管理单例)核心代码:
上传文件的方法及实现
.h
- (void)uploadWithFile:(CKGFileModel *)file VC:(UIViewController *)vc message:(Message *)message;
.m
#pragma mark - 上传功能- (void)uploadWithFile:(CKGFileModel *)file VC:(UIViewController *)vc message:(Message *)message{ _holdVC = vc; //获取文件分片数组 fileStreamer = [[FileStreamOperation alloc] initFileOperationAtPath:[self getFilePath:file.fileName] forReadOperation:YES]; if (fileStreamer.fileFragments.count) { [self uploadWithFragment:fileStreamer.fileFragments file:file message:message]; }}//上传文件分片- (void)uploadWithFragment:(NSArray *)fragments file:(CKGFileModel *)file message:(Message *)message{ dispatch_group_t group = dispatch_group_create (); //创建上传缓存路径 [[CKGFileUploadCachedManager sharedInstance] createUploadDirectoryPath]; //创建上传的文件路径 md5Str+fileName [[CKGFileUploadCachedManager sharedInstance] createUploadFilePath:[NSString stringWithFormat:@"%@%@",file.md5Str,file.fileName]]; //检查该文件是否在本地沙河中存在,文件名格式为文件md5Str+fileName NSString *filePath = [[CKGFileUploadCachedManager sharedInstance] getFilePath:[NSString stringWithFormat:@"%@%@",file.md5Str,file.fileName]]; NSArray *tempFragments; if ([[CKGFileUploadCachedManager sharedInstance] fileIsExist:filePath]) { //存在,说明是断点续传,只需要上传本地之外的文件分片 //获取还没上传的文件分片数组 tempFragments = [self getNotUploadedFragments:file]; }else { //不存在,整个文件分片都下载; //所有分片写入到沙盒 for (int i=0; i<fragments.count;i++) { NSString *fragPath = [filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%d",i+1]]; NSData *data = [fileStreamer readDateOfFragment:fragments[i]]; [[CKGFileUploadCachedManager sharedInstance] writeFileWithData:data toPath:fragPath]; } tempFragments = fragments; } dispatch_queue_t queue = dispatch_queue_create("fragment.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{//上传请求 CKGFileUploadRequest *fragmentRequest = [[CKGFileUploadRequest alloc] initWithFragments:fragments file:file fileStreamer:fileStreamer isChatSend:_isChatSend message:message]; fragmentRequest.finishedBlock = ^(CKGFileModel *fd,NSArray *fgts){ //合并请求 NSLog(@"********************%@分片上传完成",file.fileName); [self combineWith:fgts file:fd message:message]; }; });}//上传文件分片,所有片都上传完毕后合并- (void)combineWith:(NSArray *)fragments file:(CKGFileModel *)file message:(Message *)message{ NSString *urlString = [BASE_URL stringByAppendingString:@"/api/upload/formFile"]; NSString *myId = [UD objectForKey:UID_KEY]; // //文件名去掉后缀 NSMutableString *tempStr = [NSMutableString stringWithString:file.fileName]; NSString *extStr = [NSString stringWithFormat:@".%@",file.fileExt]; NSRange range = [tempStr rangeOfString:extStr.lowercaseString]; [tempStr deleteCharactersInRange:range];//// file.fileName = tempStr; NSDictionary *postData = @{ @"uid":CheckStringValue(myId), @"act":CheckStringValue(@"join"), @"name":CheckStringValue(tempStr), @"ext":CheckStringValue(file.fileExt), @"size":CheckStringValue(file.size), @"trunk_count":@(fragments.count), @"md5":CheckStringValue(file.md5Str) }; ASIFormDataRequest *__weak request = [CKGHttpBase requestWithURLString:urlString andPostDictionary:postData]; request.delegate = self; [request startAsynchronous]; [request setCompletionBlock:^{ NSString *string = request.responseString; NSDictionary *dataDic = [string objectFromJSONString]; DLog(@" bb dataDic:%@",dataDic); if ([dataDic[@"result"] integerValue]==1 && [dataDic[@"data"][@"file_exists"] integerValue]==1) { NSString *storageID = dataDic[@"data"][@"storage_id"]; NSDictionary *fileDic = @{@"storage_id":storageID,@"name":tempStr}; file.storageId = storageID; file.fileUrl = dataDic[@"data"][@"url"]; //上传到云端 if (_isSend) { self.sendBlock(file,storageID,message); }else{ [self uploadToCloudWithFileDic:fileDic file:file]; } } }];}#pragma mark - 上传到云端- (void)uploadToCloudWithFileDic:(NSDictionary *)fileDic file:(CKGFileModel *)file{ NSString *dir_id = _dir_id.length?_dir_id:@"0"; [CKGHttpManager cloudUploadFileWithUid:@"" dir_id:dir_id files:fileDic andBlock:^(BOOL resultBool, NSString *message, NSDictionary *data) { _dir_id = @"0"; if (resultBool == 1) { [NC postNotificationName:@"kMyFileCurrentVCRefresh" object:nil]; self.cloudUploadBlock(file,YES); }else { [_holdVC.navigationController.view addHUDLabelView:[NSString stringWithFormat:@"%@上传失败",file.fileName] Image:nil afterDelay:1]; self.cloudUploadBlock(file,NO); } }];}
#import <Foundation/Foundation.h>@interface CKGFileUploadCachedManager : NSObject{ NSFileManager *_fm;}+ (instancetype)sharedInstance;//判断文件是否存在- (BOOL)fileIsExist:(NSString *)fileName;//获取文件路径- (NSString *)getFilePath:(NSString *)fileName;//获取该文件路径下所有文件名- (NSArray *)getAllFileNameInPath:(NSString *)fileName;//创建上传路径- (void)createUploadDirectoryPath;//创建每个上传文件的文件夹- (void)createUploadFilePath:(NSString *)fileName;//写入文件分片到对应路径- (void)writeFileWithData:(NSData *)fragment toPath:(NSString *)filePath;//删除文件分片- (void)removeFileByName:(NSString *)fileName fragment:(NSInteger)fragmentIndex;//删除文件- (void)deleteDirctoryAtPath:(NSString *)filePath;@end
#import "CKGFileUploadCachedManager.h"static CKGFileUploadCachedManager *fileManager = nil;@implementation CKGFileUploadCachedManager- (instancetype)init{ self = [super init]; if (self) { _fm = [[NSFileManager alloc] init];; } return self;}+ (instancetype)sharedInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!self) { fileManager = [[CKGFileUploadCachedManager alloc] init]; } }); return fileManager;}#pragma mark - 是否存在该路径- (BOOL)fileIsExist:(NSString *)fileName{ //获取缓存目录下的路径 NSString *filePath = [self getFilePath:fileName]; return [_fm fileExistsAtPath:filePath];}#pragma mark - 获取文件路径- (NSString *)getFilePath:(NSString *)filename{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachesDirectory = [paths objectAtIndex:0]; NSString *lastUploadPath = [cachesDirectory stringByAppendingPathComponent:@"Upload"]; NSString *filePath = [lastUploadPath stringByAppendingPathComponent:filename]; return filePath;}#pragma mark - 获取该文件路径下所有文件名- (NSArray *)getAllFileNameInPath:(NSString *)fileName{ NSMutableArray *fileNames = [[NSMutableArray alloc] initWithCapacity:0]; NSString *path= [self getFilePath:fileName]; // 要列出来的目录 NSDirectoryEnumerator *myDirectoryEnumerator; myDirectoryEnumerator=[_fm enumeratorAtPath:path]; //列举目录内容,可以遍历子目录 NSMutableString *subPath = [[NSMutableString alloc] initWithCapacity:0]; while((subPath=[myDirectoryEnumerator nextObject])!=nil) { NSLog(@"%@",subPath); NSString *deleteStr = [NSString stringWithFormat:@"%@/",path]; [subPath deleteCharactersInRange:[subPath rangeOfString:deleteStr]]; [fileNames addObject:subPath]; } return (NSArray *)fileNames;}#pragma mark - 创建UPLOAD根文件夹- (void)createUploadDirectoryPath{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachesDirectory = [paths objectAtIndex:0]; NSString *lastUploadPath = [cachesDirectory stringByAppendingPathComponent:@"Upload"]; BOOL fileExists = [_fm fileExistsAtPath:lastUploadPath]; if (!fileExists) {//如果不存在则创建 [_fm createDirectoryAtPath:lastUploadPath withIntermediateDirectories:YES attributes:nil error:nil]; }}#pragma mark - 创建每个上传文件的文件夹- (void)createUploadFilePath:(NSString *)fileName;{ NSString *lastUploadPath = [self getFilePath:fileName]; BOOL fileExists = [_fm fileExistsAtPath:lastUploadPath]; if (!fileExists) {//如果不存在则创建 [_fm createDirectoryAtPath:lastUploadPath withIntermediateDirectories:YES attributes:nil error:nil]; } }#pragma mark - 写入文件分片- (void)writeFileWithData:(NSData *)fragment fragmentIndex:(NSInteger)fragmentIndex fileName:(NSString *)fileName{ NSString *tempPath = [[self getFilePath:fileName] stringByAppendingPathComponent:[NSString stringWithFormat:@"%zi",fragmentIndex]]; //写入 [fragment writeToFile:tempPath atomically:YES];}#pragma mark - 删除文件分片- (void)removeFileByName:(NSString *)fileName fragment:(NSInteger)fragmentIndex{ NSString *fragmentPath = [[self getFilePath:fileName] stringByAppendingPathComponent:[NSString stringWithFormat:@"%zi",fragmentIndex]]; DLog(@"删除分片路径************:%@",fragmentPath); NSError *err = nil; if (![_fm removeItemAtPath:fragmentPath error:&err]) { NSLog(@"removeItem failed, err:%@", err); }}#pragma mark - 删除文件- (void)deleteDirctoryAtPath:(NSString *)fileName{ NSString *filePath = [[self getFilePath:fileName] stringByAppendingPathComponent:fileName]; [_fm removeItemAtPath:filePath error:nil];}@end
CKGFileUploadCachedManager
CKGFileUploadCachedManager
CKGFileUploadCachedManager
- 大文件分片断点上传显示进度条
- HttpUrlconnection 实现大文件分片断点上传,支持Https
- JS异步文件分片断点上传
- webuploader分片上传大文件
- webupload大文件分片上传
- 大文件分片上传服务器
- 文件上传进度条显示
- 实现NeatUpload大文件上传和个性显示进度条
- Bootstrap file-input 插件使用(大文件上传显示进度条)
- Android大文件断点上传
- html5 大文件断点上传
- webuploader 实现大文件 分片上传
- iOS大文件分片上传和断点续传
- 大文件分片上传和断点续传
- iOS大文件分片上传和断点续传
- Python实现大文件分片上传
- 大文件带进度条上传
- 大文件上传进度条实现
- HDU 1166 敌兵布阵 (初学线段树 || 初学树状数组)
- spring拦截器实现分析
- SQL 简介
- 一句话总结行测资料分析八大速算技巧
- 低版本python如何安装 pip
- 大文件分片断点上传显示进度条
- 机器学习几种方式
- #387 The Smallest Difference
- 逆转单链表
- kubernetes源码阅读之apiserver
- MySQL数据库char与varchar的区别分析及使用建议
- 大众被曝存系统漏洞 黑客可在60秒内解锁1亿辆大众汽车
- 多军基本技能 之 无限房票攻略1.0
- 迷瘴