APP上传图片至七牛的逻辑梳理

前言

项目中有一处功能是从相册中选取图片,然后上传至七牛服务器。这儿是以前别人做的,上次功能有小改动,发现这儿的逻辑有些复杂,但当时没时间就没仔细研究。今天有时间捋了一下这里的代码逻辑,捋一遍自然是有收获的,不仅梳理了逻辑,也发现了点问题和需要改进的地方。


功能逻辑

pickPhoto.gif

该功能是这样的:首先,这是一个表单界面,表单最后一项需要上传身份证的正反面照片,即需要上传两张图片。然后该界面下面有个“提交”按钮,用于提交该表单界面的数据给服务器。

但请注意,在这里选中的两张图片提交给公司服务器时,是提交什么?应该是提交该图片的访问路径url。也就说在提交表单之前我们得拥有这两张所选图片的访问路径url。但这俩所选图片均是本地相册图片,哪里来的url呢?其实,我们需要将图片上传至七牛服务器,然后就能搞到访问该图片对应的访问路径url。公司的服务器关于该图片的信息只有其url,并无图片本身。客户端若要显示该图片,均是以该url访问所得。
简而言之,就是说公司服务器如果自己来维护图片的存储很麻烦且成本高,而七牛则是专门干这个的,成本低。那就把图片交给七牛服务器来存储,而我们自己服务器则值需要存储这个图片在七牛的访问路径就可以了。

这里的具体逻辑如下图所示:

屏幕快照 2016-11-09 22.37.34.png

首先,我们在APP上从相册选取了图片后,首先进行压缩,然后将UIImage转换为NSData,再将此图片以NSData的形式存入本地文件,等待将其上传至七牛服务器。但是要将图片上传至七牛服务器,我们得同时携带tokenkey作为凭证。凭证从哪里来?

凭证从我们的服务器来,APP请求公司服务器获取凭证,然后服务器返回给APP凭证。此时,APP就可以携带着凭证往七牛服务器上传本地图片文件了。上传成功后,七牛响应成功信息,其中包含一个key字段,表示该图片的唯一标识。

别忘了,我们的目的不仅仅是将APP本地图片上传至七牛,我们还得获得访问已然存入七牛的图片的路径。不然上传还有什么意义,上传完后丧失连接了方式,像失踪在汪洋宇宙。APP以该key为参数,向公司服务器请求该key对应图片的url。然后服务器返回该图片对应的url,那我们以后显示该图片就是访问该url,提交该图片就是提交该url。(疑问:公司服务器是怎么获得该key对应图片的url的?)


代码

我们把它的逻辑捋清楚了,看起代码来也就轻松愉快了很多。

#import "PickPhotoDemoViewController.h"
#import "YWImagePickView.h"
#import "MLSelectPhotoPickerViewController.h"
#import "MLSelectPhotoAssets.h"
#import "PhotoModel.h"
#import "QNUploadManager.h"
#import "YWAFNetManager.h"


#define kImageFilePath(name) [NSTemporaryDirectory() stringByAppendingPathComponent:name] // 图片路径
#define Photo_W 80.f
@interface PickPhotoDemoViewController ()

@property (nonatomic, strong) NSMutableArray *photoList;
@property (nonatomic, strong) YWImagePickView *imgPickView;

@end

@implementation PickPhotoDemoViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.contentView addSubview:self.imgPickView];
}



#pragma mark - init

- (NSMutableArray *)photoList
{
    if (_photoList == nil) {
        _photoList = [NSMutableArray array];
    }
    return _photoList;
}


- (YWImagePickView *)imgPickView{
    if(!_imgPickView){
        _imgPickView = [[YWImagePickView alloc] initWithFrame:CGRectMake(20.f, 100.f, PDWidth_mainScreen-40.f, Photo_W)];
        _imgPickView.imgLength = Photo_W;
        _imgPickView.eachRowCount = 2;
        __weak PickPhotoDemoViewController *weakSelf = self;
        _imgPickView.addPhotoBtnBlock = ^(){
            [weakSelf openPhotoAlbum];
        };
        __weak YWImagePickView *weakImgPickV = _imgPickView;
        _imgPickView.deletePhotoBtnBlock = ^(NSInteger index){
            [weakSelf.photoList removeObjectAtIndex:index];
            [weakImgPickV refreshContent:weakSelf.photoList];
        };
    }
    
    return _imgPickView;
}




#pragma mark - function

// 打开相册,选取图片
- (void)openPhotoAlbum
{
    MLSelectPhotoPickerViewController *pickerVc = [[MLSelectPhotoPickerViewController alloc] init];
    // 默认显示相册里面的内容SavePhotos
    pickerVc.status = PickerViewShowStatusCameraRoll;
    pickerVc.minCount = 2 - self.photoList.count;
    [pickerVc showPickerVc:self];
    __weak typeof(self) weakSelf = self;
    pickerVc.callBack = ^(NSArray *assets){
        for(int i=0; i<assets.count; i++)
        {
            MLSelectPhotoAssets *asset = assets[i];
            UIImage *newImage = [[self class] imageWith:asset.originImage scaledToSize:CGSizeMake(Photo_W, Photo_W)]; // 图片压缩
            NSData *imageData = UIImageJPEGRepresentation(newImage, 1);// UIImage -> NSData
            NSString *fileName = [NSString stringWithFormat:@"%d.png",arc4random()];
            [imageData writeToFile:kImageFilePath(fileName) atomically:YES]; // 将NSData写入本地文件
            
            [weakSelf requestUploadTokenFileName:fileName]; // 请求上传图片所需的token
        }
    };
}



// 申请图片上传的token
- (void)requestUploadTokenFileName:(NSString *)fileName
{
    NSDictionary * parameters=[[NSDictionary alloc] initWithObjectsAndKeys:
                               @(1), @"count",
                               nil];
    
    __weak typeof(self) weakSelf = self;
    NSString *url = [NSString stringWithFormat:@"%@/cbs/%@/upload/token",HTTPSURLEVER,Interface_Version];
    url =[url stringByReplacingOccurrencesOfString:@"http" withString:@"https"];
    
    YWAFNetManager *manager = [YWAFNetManager manager];
    [manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSDictionary *paramDict = DealWithJSONValue(responseObject[@"b"][0]);
        [weakSelf uploadPhotoFile:paramDict fileName:fileName]; // 拿到了token,上传图片数据
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [[CustomTips shareInstance] showAutomaticTipsMessage:@"图片token获取失败,请重新选择图片" compelte:nil];
    }];
    
    
}


//上传图片数据到七牛
- (void)uploadPhotoFile:(NSDictionary *)params fileName:(NSString *)fileName
{
    QNUploadManager *uploader = [[QNUploadManager alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *token = params[@"uploadToken"];
        NSString *key = params[@"key"];
        __weak typeof(self) weakSelf = self;
        
        [uploader putFile:kImageFilePath(fileName) key:key token:token complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
            // 把key赋值给photoModel的photoKey
            PhotoModel *photoModel = [PhotoModel new];
            photoModel.photoKey = key;
            [self.photoList addObject:photoModel];
    
            [weakSelf getPhotoUrlWithKey:resp[@"key"]]; // 请求公司服务器,获得该图片对应的url
            
        } option:nil];
    });

}



// 以key为参数获取图片的url
- (void)getPhotoUrlWithKey:(NSString *)key
{
    NSDictionary * parameters=[[NSDictionary alloc] initWithObjectsAndKeys:
                               key, @"token",
                               nil];
    NSString *url = [NSString stringWithFormat:@"%@/cbs/%@/upload/url",HTTPSURLEVER,Interface_Version];
    url =[url stringByReplacingOccurrencesOfString:@"http" withString:@"https"];
//  __weak typeof(self) weakSelf = self;
    YWAFNetManager *manager = [YWAFNetManager manager];
    [manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
        if (!responseObject[@"b"] || [responseObject[@"b"] isEqual:[NSNull null]]) {
            return;
        }
        NSString *photoUrl =[NSString stringWithFormat:@"%@",responseObject[@"b"][@"url"]];
        
        // 把url赋值给PhotoModel的photoUrl
        PhotoModel *tempPhoto = [self.photoList lastObject];
        tempPhoto.photoUrl = photoUrl;
        
        [_imgPickView refreshContent:self.photoList];
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [self.photoList removeAllObjects];
        [[CustomTips shareInstance] showAutomaticTipsMessage:@"图片上传失败,请重新选择" compelte:nil];
    }];
    
}



//压缩图片
+ (UIImage*)imageWith:(UIImage*)originImage scaledToSize:(CGSize)newSize
{
    UIGraphicsBeginImageContext(newSize);
    [originImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}


@end

主要代码放在了百度网盘:APP上传图片至七牛


问题改进

当前的执行逻辑是:用户在APP相册选取了图片后,立马去请求凭证,然后携带着凭证将图片上传至七牛服务器,然后再请求该图片对应的访问路径。当拿到自己服务器返回的图片访问路径后,才去刷新界面,才在_imgPickView上显示出所选取的几张图片的缩略图。而且,显示图片的方式是加载该图片已获得的url。然后,当用户点击“提交”按钮时,将表单提交给服务器,提交的是图片的url

这样不合理。其一是用户是在本地选取的图片且已存入了本地文件,为什么在界面显示还要加载url,万一网络不好,用户在相册选取个照片,竟然还要加载半天才显示出图片?!其二是只要选取了照片就立马上传,这是有问题的,会造成不必要的上传开销。比如,用户选了图片后又删除重新选了图片,这样被删除的那个图片其实也已经上传至七牛了。

所以,** 正确的逻辑应当是: 用户在相册选取了图片后,应该立马加载本地的图片刷新界面,先仅仅让所选取的图片显示出来。然后直到用户点击了“提交”按钮后,再去执行“获取凭证,上传图片,获取图片访问路径”这套逻辑代码。等这套都执行完了后,此时客户端拿到了所选取图片的访问路径,然后再把表单提交给服务器。**

代码基本上不变,只是调用顺序的改变。所以就不写代码了。


结尾

深圳这几天变凉了,天凉注意加衣。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,411评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 1、输入框输入长度限制 达到长度的最大值就不能在输入了,不需要任何js 2、Input 上传文件: 上传文件的格式...
    Tina任阅读 9,690评论 0 1
  • 在这个诡异寒潮来袭的三月,《疯狂动物城》几乎刷屏了所有的社交软件,迪斯尼给世界带来了的惊喜和温暖。我打破了从不去电...
    骑蜗牛的小蓝帽阅读 1,936评论 0 0