Node开发文件上传系统及向七牛云存储和亚马逊AWS S3的文件上传
背景起,有奏乐:
有伟人曰:学习技能的最好途径莫过于理论与实践相结合。
初学Node这货时,每每读教程必会Fall asleep。
当真要开发系统时,顿觉精神百倍,即便踩坑无数也不失斗志。
因为同团队的小伙伴们都在辛勤工作,正是因为他们的工作,
才让我有足够的时间拖着我疲软的智商来研究Node和AWS这些货。
系统完成,虽不尽完善,但不敢怠慢,迅速记录,免遗忘。
为后续更新和开发做一参考。
这就是人生。只要努力,便美美哒。
标题略长,其实这系统要做的事只三件:
1. 从本地上传文件到我们自己的服务器,并存储。
2. 将文件上传到七牛云存储。
3. 将文件上传到亚马逊的AWS S3存储。
几处说明:
1. 用Node的好处是写服务端代码也不用纠结语法问题了:
系统的开发用Node完成。写前后端都是JS,免去了语法的困扰。
不仅回忆起数日之前写Scala时对语法的纠结和困惑,一身冷汗。
2. Plupload是个好东东:
Client端的File Select用Plupload完成。
有了Plupload这货,再不纠结<input type='file'>的难看样式的兼容问题不好把控了。
Plupload虽然对File做了封装,但也提供了如 getNative 等的接口供我们访问原生。
十分体贴。
3. AWS的Upload在前端完成:
真相只有一个:在Node服务端的AWS的Upload我还没跑通……
请尽情的鄙视我吧T_T
好在路路通罗马。我绕路从前端赶到了罗马。
服务端请求的Block在这里:
从服务端向AWS上传文件时,其文件的Body以流方式被分块上传。
测试后发现,上传完成,也只传了部分,导致文件无法正常访问。
而在前端上传时,直接用原生File对象即可实现上传。
遂成功抵达罗马。
关于在服务端的上传问题,有待继续研究。
学海无涯0_0
4. 七牛的上传在服务端完成:
七牛的上传也可以在前端完成,只不过七牛自己的JS-SDK包裹了Plupload。
由于我的上传逻辑是由自己的Plupload来触发七牛和亚马逊(或其他第三方上传),
因此不在前端再New一个Plupload来做七牛的上传了。
New两个同样的东西实在是太二了好么。
设计的理念是,所有第三方上传都必须在我们的服务器Trigger之后才发生。
就酱任性。
—————— 我是冬季里颤巍巍的分割线 ——————
主要逻辑和部分代码:
1. 主程序和框架:
使用Express框架和Jade渲染引擎。
主程序app.js只做服务器的创建和监听,
涉及业务逻辑的请求和处理,都写在二级目录(./routes)的模块里。
app.js 的部分内容如下:
3 var express = require('express'); 4 var favicon = require('serve-favicon'); 5 var bodyParser = require('body-parser'); 6 var debug = require('debug')('express:server'); 7 var http = require('http'); 8 var port = normalizePort(process.env.PORT || '3038'); 9 var app = express(); 10 var server = http.createServer(app); 11 var index = require('./routes/index'); // 业务逻辑在这里 12 13 app.set('port', port); 14 server.on('error', serverOnError); 15 server.on('listening', serverOnListening); 16 server.on('connection', serverOnConnecting); 17 server.listen(port); 18 19 app.set('views', path.join(__dirname, 'views')); 20 app.set('view engine', 'jade'); 21 app.use(favicon(path.join(__dirname, 'public/lib', 'favicon.ico'))); 22 app.use(bodyParser.json()); 23 app.use(bodyParser.urlencoded({ extended: true })); 24 app.use(express.static(path.join(__dirname, 'public'))); 25 26 app.use('/', index);
1 /* ====================================================== */
2 module.exports = app;
2. POST请求将文件上传并存储在本地服务器:
需要注意的是,这里的POST请求用到了中间件:
1 var multipart = require('connect-multiparty'); 3 var multipartMiddleware = multipart(); 5 var express = require('express'); 7 var router = express.Router(); 9 router.post( ‘/saveInLocalServer’, multipartMiddleware, function(req, res){ 。。。});
这个请求接收的是从前端的Plupload上传的File,
神秘的中间件会在服务器生成临时文件,但不会删除它们。
因此在处理的最后要手动删除临时文件req.files。How to?
收到请求后,处理文件的部分代码如下:
1 var file = req.files.file; 2 var tempPath = file.path, 3 fileName = file.name, 4 fileType = file.type, 5 fileSize = file.size; 6 var uploadDirName = dirName.DirName; // 生成目录的模块,每月一生 7 var filenameWithMd5 = MD5( new Date().getTime() ) + '-' + fileName; 8 var filenameForCloud = fileRename.FileRename(fileName); 9 // 保存到本地服务器的文件,使用MD5重命名文件 10 // 上传到云存储的文件,使用自定义的模块重命名 11 var targetPath = path.resolve('./' + uploadDirName + '/' + filenameWithMd5); 12 // Save file in our local server: 13 fs.rename(tempPath, targetPath, function(err, data){ 14 if( err ){ 15 var result = 'error'; 16 res.status( result ).send(); 17 } else { 18 var result = 'ok'; 19 var uploadInfos = { ... }; // AWS的config信息定义在服务端,由模块引入并发送到前端,供JS接口调用: 20 res.status( result ).send( uploadInfos ); 21 // Next do Qi Niu Upload ... blah blah blah 22 } 23 });
针对上述代码的几处说明:
a:关于在本地服务器生成目录:
我们的需求是,每月首次触发上传动作时,在服务器创建一只新目录。
该月内的其余上传文件,都存储在这一目录里。
所有的文件会按上传时间,以自然月为目录而分类。
按月创建目录的逻辑,我写了一枚小小模块,如下:
var fs = require('fs');var _d = new Date(); var _year = _d.getFullYear(); var _month = (_d.getMonth() + 1 < 10)?('0' + (_d.getMonth() + 1)):(_d.getMonth() + 1); // 为整齐,月份都显示为两位数,因此1-9月前面加0 var dir = _year + '-' + _month + '-alex_upload';if (!fs.existsSync(dir)){fs.mkdirSync(dir); }exports.DirName = dir; // 输出模块名为DirName// ============================ // 假设这个文件名为makeDirName.js,则在业务逻辑中引入并应用要这样: var d_name = require('../routes/makeDirName'); var someName = d_name.DirName; // 输出的模块名在这里被这样引用
b:关于文件重命名:
我们的需求是,存在本地服务器的文件,使用MD5重命名。
上传到云存储的文件,使用时间戳和随机字符串共同重命名。
重命名文件的模块是酱紫写的:
1 function rename( filename ) { 2 var name = ''; 3 var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 var length = 6; // 随机字符串的长度,暂用6 5 for(var i = 0; i < length; i++){ 6 name += possible.charAt( Math.floor(Math.random() * possible.length) ); 7 } 8 var timestamp = new Date().getTime(); 9 name = timestamp + '-' + name + '-' + filename; 10 return name; 11 }; 12 13 exports.FileRename = rename;
c:关于res.status( 200 ).send( data ):
每个请求的response必须Call一下res.end(),
以此来告诉服务器这个请求的header和body都已发送,
并且这个请求已经完成。
如果不告诉服务器,呆萌的服务器是永远不会知道的。
浏览器会一直在请求状态中,标题栏的小圈圈一直在转啊转,
表示请求一直在持续啊持续。
在Call了res.end()之后,res.finished 的值为true,否则是false。
res.send() 会Call res.end(),因此不需重复Call。
3:在前端请求AWS S3
在发送刚才所提到的POST请求之前,
前端先new一个plupload的Uploader,部分代码如下:
1 var _myUploader = new plupload.Uploader({ 2 runtimes: 'html5,flash,silverlight,html4', 3 file_data_name: 'file', 4 container: _SCOPE.containerId, 5 browse_button: _SCOPE.filePickerId, 6 uptoken_url: _ELE.fileUptoken.innerHTML, 7 url: _ELE.fileLocalSave.innerHTML, 8 flash_swf_url: _SCOPE.swfUrl, 9 silverlight_xap_url:_SCOPE.xapUrl, 10 filters: { 11 max_file_size: _SCOPE.maxFileSize, 12 mime_types: [ 13 {title: 'Image files', extensions: 'jpg,png,gif'}, 14 {title: 'Zip files', extensions: 'zip'} 15 ] 16 }, 17 init: {...} 18 });
这里的 _SCOPE 和 _ELE 定义在全局作用域,或指定页面模块作用域下。
目的是从服务端接收相关的配置参数,在页面发送请求时调用。
这里遵循了一个高端大气上档次的写码原则,即:
常量参数的配置,
如Domain地址、取token之通信接口、
even 账户的accessKey&accessToken blah blah blah……
都在服务端某指定模块内统一配置。
当前端需要某参数时,由页面渲染res.render() 传递到页面元素HTML属性里,
但是不可以将Key等账户密钥渲染在页面结构里。
也可以通过前后端通信将参数传递给前端页面,
例如刚才所述的POST接口里的uploadInfos。
这样做,在一处定义,其余皆调用。
当值有更新时,只在定义处更新其值即可。
避免多处赋值,更新时丢三落四陷入混乱。
嗯咳,所有工程师都知道的好么!我说多了……
……继续说上传:
使用Plupload,在其FileUploaded 的回调里,
即可执行向AWS S3发送请求了。
FileUploaded是在Plupload的文件上传成功后才会触发。
前端请求AWS S3的简要方法如下:
(这里的file是从FileUploaded的方法里用getNative获取到的原生file对象)
1 function doAWSUpload( rename, file, info ) { 2 var file_name = file.name, 3 file_type = file.type, 4 file_size = file.size; 5 var bucket = new AWS.S3(); 6 var uniqueName = rename; 7 bucket.config.update({ // 配置信息,在服务端传来的info里 8 accessKeyId: info.accessKeyId, 9 secretAccessKey: info.secretAccessKey 10 }); 11 bucket.config.region = info.region; 12 var params = { 13 Bucket: info.bucket, // 账户指定的bucket名 14 Key: uniqueName, 15 ContentType: file_type, 16 Body: file, ACL: 'public-read', // 设置文件访问权限 17 ServerSideEncryption: info.ServerSideEncryption 18 }; 19 bucket.putObject(params, function(err, data){ // 此账户必须要有putObject的操作权限才能调用 20 if(err){ 21 var errText = ' ' + file_name + ' failed in uploading to AWS! ' + err; 22 _ELE.fileConsole.innerHTML += errText; 23 }else{ 24 var url = 'https://s3.amazonaws.com/' + info.bucket + '/' + uniqueName;26 _ELE.fileConsole.innerHTML += ' AWS upload succeeded! ' + url; 27 } 28 }).on('httpUploadProgress', function(progress){ 29 console.log( 'AWS uploading...', Math.round(progress.loaded / progress.total * 100) ); 30 }); 31 };
执行这个方法的前提是前端页面调用了JS-SDK,
并且,……最重要的是并且:
对应账户在AWS的Console管理后台的相关配置要正确。
最讨厌各种相关配置了,
配来配去一百年才成功一次……
4:AWS的账户在Console管理后台的相关配置
首先注册一枚高大上的AWS账户。
如果你经常在Amazon上买买买,也可以用你的Retail账户。
开通AWS服务,需要验证,其过程要填写Payment账户信息。
我十分Naive的填了自己的Credit Card信息,结果直接被扣掉1刀勒。
吓尿之后,立刻删。
大约因为作为Retail账户时我曾做过快捷支付神马的脑残设置吧。
总之,1美元而已,这已不是重点……
有了一枚飘逸的AWS账户后,登录 https://console.aws.amazon.com
选择S3服务,进来后无视一切,先Create Bucket,
点击这个新的Bucket,选择Properties,
在Permissions里,再选择 “Edit CORS Configuration”,
一个较为典型的CORS Configuration可以长这个样子:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 3 <CORSRule> 4 <AllowedOrigin>http://localhost:3038</AllowedOrigin> //本地测试入口 5 <AllowedOrigin>http://shaojing.wang</AllowedOrigin> //线上测试入口 6 <AllowedMethod>PUT</AllowedMethod> //可执行的方法 7 <AllowedMethod>DELETE</AllowedMethod> //可执行的方法 8 <MaxAgeSeconds>3000</MaxAgeSeconds> 9 <ExposeHeader>x-amz-server-side-encryption</ExposeHeader> 10 <ExposeHeader>x-amz-request-id</ExposeHeader> 11 <ExposeHeader>x-amz-id-2</ExposeHeader> 12 <AllowedHeader>*</AllowedHeader> 13 </CORSRule> 14 </CORSConfiguration>
这里的CORS Configuration即对跨域请求所做限制,
只有“AllowedOrigin”里指定的端口才能向AWS发出请求,
而只有“AllowedHeader”里指定的端口才能接收请求(访问文件)。
上传成功后,可通过这样的URI访问到文件:
https://s3.amazonaws.com/myBucketName/1452581386878-hPp8Mc-test.png
附:AWS的文档在这里:http://docs.aws.amazon.com/
关于如何在服务端进行AWS S3的上传,下次再写文章分享。
下面该讲什么了……
5:在服务端实现向七牛云存储上传文件
该七牛了。
请八牛、九牛和十牛再耐心等一等。
六牛你不要闹,你已经谢世了好么。
从服务器向七牛云发送请求之前,需要获取授权,
请求授权之前,需要设置账户信息。
设置账户信息之前,你得先有一枚账户。
有了账户就有了AccessKey & SecretKey。
还是刚才讲的,在统一配置参数的模块里,配置好这些Key们的信息,
然后在服务端将发送请求之前,做赋值:
1 var qiniu = require('qiniu'); 2 var qnConf = require('../config/qiniu_config'); 3 4 /* Prepare Qiniu config, we make Qiniu upload in Node Server not in browser*/ 5 qiniu.conf.ACCESS_KEY = qnConf.QiniuConfig.ACCESS_KEY; 6 qiniu.conf.SECRET_KEY = qnConf.QiniuConfig.SECRET_KEY;
赋值之后,就可以开心的去请求upToken了!
写一只孤零零的单独小模块,用来生成upToken,代码长这样:
1 var qiniu = require('qiniu'); 2 3 function uptoken(bucketname) { // 指定一个bucket传名字进来 4 var putPolicy = new qiniu.rs.PutPolicy(bucketname); 5 return putPolicy.token(); 6 } 7 8 exports.Uptoken = uptoken;
拿到upToken就可以华丽丽丽丽的开始上传了。
可以在刚才本地存储的POST请求成功后的回调里做。
代码就像酱紫:
1 // Do Qiniu upload in here: 2 var targetPath = path.resolve('./' + uploadDirName + '/' + filenameWithMd5); //接刚才的POST里的处理 3 var qiniu_uptoken = generateUptoken.Uptoken(qnConf.QiniuConfig.Bucket_Name); 4 var extra = null; // 放额外信息,先写null 5 fs.readFile(targetPath, function(error, data){ 6 qiniu.io.put(qiniu_uptoken, uploadDirName + '/' + filenameForCloud, data, extra, function(err, ret){ 7 if(err){ 8 console.log('Something is wrong with Qiniu upload! ', err); 9 }else{ 10 console.log('qiniu: ', ret); 11 console.log('Qiniu URL = ', qnConf.QiniuConfig.Domain + uploadDirName + '/' + filenameForCloud); //手动拼结果URL 12 } 13 }); 14 });
至此,七牛的上传也OK鸟!
撒花~~乐队起~~
5:后记
本文所述内容,仅限于最主要最基本的逻辑,
未涉及页面的交互和部分异常响应的处理。
仅供参考。表扔鸡蛋。
转载于:https://www.cnblogs.com/alex1128/p/nodeToUpload.html
Node开发文件上传系统及向七牛云存储和亚马逊AWS S3的文件上传相关推荐
- Ubuntu 配置亚马逊 aws cli 上传文件文件夹至 亚马逊 AWS S3
当使用亚马逊云服务器进行深度学习模型训练时,需要将数据集上传,相比使用 UI 界面上传,使用命令行方式具有更快的上传速率. 配置步骤 安装 aws cli 客户端: pip install awscl ...
- windows系统上利用putty通过SSH连接亚马逊AWS服务器
1. 找到在购买亚马逊的AWS服务器时保存的密钥文件(假设为abc.pem). 2.打开PuTTYgen,如下图,点击图中1处的"load",找到abc.pem文件所在的位置,并选 ...
- 亚马逊主图视频可以上传几个?有什么要求?
关于亚马逊主图视频新手们,往往也搞不清楚就是主图视频到底可以上传几个?那么, 接下来,我们就来说下,关于亚马逊主图视频的介绍,想知道的朋友们就可以花点时间来做个详细的内容了解了.有什么要求? 允许卖家 ...
- 乐鑫esp8266学习rtos3.0笔记:AT指令固件如何二次开发,实现AT指令连接亚马逊AWS IoT平台;
本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 基于C SDK的ESP8266开发技术全系列笔记 一.N ...
- AI大事件 | 谷歌的计算引擎鸟枪换炮用上了更快的GPU,基于Python的亚马逊AWS深度学习AMI
大数据文摘作品 编译 | 宁云州 呜啦啦啦啦啦大家好呀,又到了本周的AI大事件时间了.过去的一周中AI圈都发生了什么?大佬们互撕了哪些问题?研究者们发布了哪些值得一读的论文?又有哪些开源的代码和数据库 ...
- 云计算为教育插上翅膀 北飞科技与亚马逊AWS奏响教育均衡协奏曲
破解教育均衡难题,互联网+教育已经在路上. 今年的政府工作报告指出,发展更加公平更有质量的教育.其中,"发展'互联网+教育',促进优质资源共享"被着重强调.在实现教育均衡上,包括云 ...
- 触控科技携手亚马逊AWS和应用商店 提升初创游戏开发公司盈利水平
随着互联网世界的日益庞大,基于互联网有关增加.使用和交付模式的云服务成为互联网行业发展的最新趋势,国内的云服务产业正以一种前所未有的速度发展.演化.今年,国内领先的研发商与发行商触控科技已在旗下的&q ...
- 激活数据潜力 亚马逊云科技重塑云上存储“全家桶”
众所周知,重塑是亚马逊云科技重要的文化,已经渗入到了企业的血液当中,在重塑文化引领下,亚马逊云科技在各个领域不断创新突破,为用户提供了丰富的产品和服务. 作为云计算的领军企业,亚马逊云科技在计算.存储 ...
- 亚马逊AWS市值何时超过所有IT公司之和并成为史上首个超万亿美元公司?
版权声明:任何转载需全文转载并保留来源(微信公众号techculture)和该声明,并同时转载文后的二维码,否则视作侵权:如有文字或图片不全,请移步公众号techculture. 这不是股市暴跌之后的 ...
最新文章
- 理解oracle中连接和会话
- bzoj1174 Toponyms
- .Netcore使用Session
- java和C操作数组的一个小区别
- PHP中用于精确计算的bcmath扩展
- 用Python实现每天向女友表白一次,甜蜜暴击,最后终于被我追到手了!太厉害了!
- 关于项目中的日期提交
- oracle会话超时,Oracle EBS控制会话时间及超时
- 拼图项目的诅咒:为什么Java 9一遍又一遍地延迟?
- 写一个Android输入法01——最简步骤
- Python之List和Tuple类型(入门3)
- 根据中序和先序遍历创建一颗二叉树☆
- 源码剖析 Netty 服务启动 NIO
- 毕业设计管理系统PHP,asp.net/net/c#毕业论文管理系统-成品
- span标签显示固定长度显示省略号,光标放上显示全部
- 支付宝第三方登录接口调用
- Java流(Stream)操作实例-筛选、映射、查找匹配
- 附解决方案,小程序用户昵称突然变成了“微信用户”,而且头像也显示不了?
- 非此即彼的逻辑错误_GMAT高分范文100篇(非此即彼逻辑错误)【圣才出品】
- 想出名 你就这样炒作自己
热门文章
- 打开应用蜂窝移动数据就关闭_基于移动应用行为数据的客户流失预测
- 许家印帮贾跃亭广州拿地造车,这是要翻身的节奏吗?
- SDCC 2016数据库峰会(深圳站)学习笔记
- Detection and Classification of Acoustic Scenes and Events(DCASE2013详细介绍)
- 64位Ubuntu kylin 16.04显示CPU内存使用率
- python的xlrd怎么安装_python接口测试,第三方包xlrd和xlutils,怎么安装
- python链表中删除一个节点数据_python实现单链表中删除倒数第K个节点的方法
- Java 调用 Caffe_解决 free(): invalid pointer: 0x00000000019ff700 运行时报错(caffe)(libtool使用)...
- c语言函数fread的调用形式,C语言的问题,fread和fgets的区别是什么?
- 【TensorFlow-windows】学习笔记三——实战准备