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
上传到后端存储文件的名称,postParems
post请求所带的参数,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网络请求做中间的处理的。