ios图片上传/下载 NodeJS后台

iOS图片上传/下载 NodeJS后台

基本所有应用中都存在图片的上传和下载,之前了解到的也就是ios端的代码,并不知道后端是如何解析收到的数据,以及下发文件的。结合NodeJS的学习,完成图片上传下载技术上的闭环。

ios端的实现

前端的实现是向请求中添加form表单,将上传的文件作为请求的body数据,发出post请求。这里给出实现的demo

+ (NSString *)postRequestWithURL: (NSString *)url
                      postParems: (NSMutableDictionary *)postParems
                     picFilePath: (NSString *)picFilePath
                     picFileName: (NSString *)picFileName
                          result:(void (^)(NSError *error, NSDictionary *resultInfo))imageHandle
{
    //住址Url
    NSURL *requestUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",MMDebugUrl,url]];

    NSString *TWITTERFON_FORM_BOUNDARY = @"0xKhTmLbOuNdArY";
    //根据url初始化request
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl
                                                           cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                       timeoutInterval:10];
    //分界线 --AaB03x
    NSString *MPboundary=[[NSString alloc]initWithFormat:@"--%@",TWITTERFON_FORM_BOUNDARY];
    //结束符 AaB03x--
    NSString *endMPboundary=[[NSString alloc]initWithFormat:@"%@--",MPboundary];
    //得到图片的data
    NSData* data;
    if(picFilePath){

        UIImage *image=[UIImage imageWithContentsOfFile:picFilePath];
        //判断图片是不是png格式的文件
        if (UIImagePNGRepresentation(image)) {
            //返回为png图像。
            data = UIImagePNGRepresentation(image);
        }else {
            //返回为JPEG图像。
            data = UIImageJPEGRepresentation(image, 1.0);
        }
    }
    //http body的字符串
    NSMutableString *body=[[NSMutableString alloc]init];
    //参数的集合的所有key的集合
    NSArray *keys= [postParems allKeys];

    //遍历keys
    for(int i=0;i<[keys count];i++)
    {
        //得到当前key
        NSString *key=[keys objectAtIndex:i];

        //添加分界线,换行
        [body appendFormat:@"%@\r\n",MPboundary];
        //添加字段名称,换2行
        [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
        //添加字段的值
        [body appendFormat:@"%@\r\n",[postParems objectForKey:key]];

        NSLog(@"添加字段的值==%@",[postParems objectForKey:key]);
    }

    if(picFilePath){
        ////添加分界线,换行
        [body appendFormat:@"%@\r\n",MPboundary];

        //声明pic字段,文件名为boris.png
        [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",FORM_FLE_INPUT,[self fileName:picFileName filePath:picFilePath]];
        //声明上传文件的格式
        [body appendFormat:@"Content-Type: image/jpge,image/gif, image/jpeg, image/pjpeg, image/pjpeg\r\n\r\n"];
    }

    //声明结束符:--AaB03x--
    NSString *end=[[NSString alloc]initWithFormat:@"\r\n%@",endMPboundary];
    //声明myRequestData,用来放入http body
    NSMutableData *myRequestData=[NSMutableData data];

    //将body字符串转化为UTF8格式的二进制
    [myRequestData appendData:[body dataUsingEncoding:NSUTF8StringEncoding]];
    if(picFilePath){
        //将image的data加入
        [myRequestData appendData:data];
    }
    //加入结束符--AaB03x--
    [myRequestData appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];

    //设置HTTPHeader中Content-Type的值
    NSString *content=[[NSString alloc]initWithFormat:@"multipart/form-data; boundary=%@",TWITTERFON_FORM_BOUNDARY];
    //设置HTTPHeader
    [request setValue:content forHTTPHeaderField:@"Content-Type"];
    //设置Content-Length
    [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[myRequestData length]] forHTTPHeaderField:@"Content-Length"];
    //设置http body
    [request setHTTPBody:myRequestData];
    //http method
    [request setHTTPMethod:@"POST"];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        if (!connectionError || data.length) {
            NSDictionary *resultDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            imageHandle(nil,resultDict);
        }
        else {
            imageHandle(connectionError,nil);
        }
    }];

    return nil;
}

demo中的post上传只适合单个文件上传,如果需要做多文件的上传。可以有两种方案来实现:

  • 循环使用这个接口
  • form表单中添加多个文件数据

从苹果的网络设计层面来说,第二个方案更加符合苹果。尽量减少请求数量,增加请求的饱和度。请求中的参数意义分别是url请求的接口,picFilePath需要上传的文件的路径,picFileName上传到后端存储文件的名称,postParemspost请求所带的参数,result请求回调

NodeJS实现

添加upload和download路由

var upload = require('./routes/upload');
var download = require('./routes/download');
...
app.use('/upload', upload);
app.use('/download', download);

增加upload post请求的解析

var express = require('express');
var router = express.Router();
var formidable = require('formidable');
var fs = require('fs');

router.post('/image',function(req,res) {
    var form = new formidable.IncomingForm();
    // req 即用户请求对象
    form.parse(req, function (err, fields, files) {
        if (files) {
            console.log(files);
            // 获得文件的临时路径
             var tmp_path = files.file.path;
            // 指定文件上传后的目录 - 示例为"images"目录。 
            var target_path = './public/images/' + files.file.name;

            if (tmp_path && target_path) {
                console.log()
                // 移动文件
                fs.rename(tmp_path, target_path, function(err) {
                  if (err) {
                      res.send({"netStatus":{"code":-1,"des":"出错了" + err}});
                  } else {
                    // 删除临时文件夹文件, 
                      fs.unlink(tmp_path, function() {
                         if (err) {
                              res.send({"netStatus":{"code":-1,"des":"出错了" + err}});
                         } else {
                             res.send({"netStatus":{"code":0,"des":"文件" + target_path + "上传成功!"}});
                         }
                      });
                  }
                });
            } else {
                 res.send({"netStatus":{"code":-2,"des":"文件丢失"}});
            }
        }
    });
})

module.exports = router;

这里有一个小插曲,nodeJS指定的bodyParser工具可以正确的解析请求中json参数,当前端指定了请求中的参数为:

[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

但是在上传文件的时候,参数类型是以表单的形式上传此时的参数类型是:

[request addValue:@"multipart/form-data" forHTTPHeaderField:@"Content-Type"];

当上传的数据的内容类型发生改变的时候,上传文件的参数无法解析,报了服务端500的错误,在经过了chrome查询后 npm install formidable第三方插件解决了这个问题。

下面是download路由的处理:

var express = require('express');
var router = express.Router();

router.get('/image/:imageName',function(req,res) {
    var target_path = './public/images/' + req.params.imageName;
    console.log("文件路径");
    console.log(target_path);
    res.download(target_path);
})

module.exports = router;

nodeJS启动服务之后,并不能直接根据文件的目录直接取出文件的内容,nodeJS中所有的请求服务都是走的GET/POST接口,所以就出现了download这个路由处理,在一般的CDN中,如果我们将文件上传到了服务器,根据指定的文件路径,就可以访问到这个文件,并完成下载,但是在NodeJS中,确实要写一层GET网络请求做中间的处理的。