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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机学业水平考试及格,信息技术学业水平考试表格部分试题(带答案)

第三章表格信息的加工与表达复习学案【学习目标】1.熟练使用excel加工表格信息&#xff0c;理解用图表来表现信息的特点与意义&#xff0c;2.能根据表格数据关系选择合适的图表类型表达意图。【考点】1.表格中常用的函数及其求值方法&#xff1b;2.根据数据选择合适的图表类型&…

Ok6410挂载NFS

虚拟机&#xff1a; apt-get install portmap apt-get install nfs-kernel-server mkdir /nfs/root/mNFS chmod 777 /nfs chmod 777 /nfs/root vi /etc/exports 添加&#xff1a;/nfs/root *(rw,sync,no_root_squash) 开发板&#xff1a; mount -t nfs 192.168.0.12…

云计算:容器技术变革云计算,SaaS带动CaaS市场

报告摘要&#xff1a; 1、容器技术增速惊人&#xff0c;市场认可度提高 虚拟化是云计算的重要基础&#xff0c;Docker定义了一套容器从构建到执行的标准化体系&#xff0c;改变了传统的虚拟化技术&#xff0c;深度影响了云计算领域。 随着谷歌、亚马逊、微软等云计算厂商纷纷加…

Jan 12 - Delete Node in a Linked List; Data Structure; Linked List; Pointer;

代码&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) { val x; }* }*/ public class Solution {public void deleteNode(ListNode node) {if(node null) return;while(node.next ! …

三年级神奇电子计算机教案,人教版小学三年级下册信息技术教案

人教版小学三年级下册信息技术教案 人教版小学信息技术教案第一课 神奇的信息世界教学目的&#xff1a;通过学习使学生更充分地了解信息技术在生活中的应用。教学内容&#xff1a;观看“神奇的信息世界”光碟教学准备&#xff1a;1、调试每台计算机 2、打开计算机并由教师机控制…

spark 安装配置

最佳参考链接 https://opensourceteam.gitbooks.io/bigdata/content/spark/install/spark-160-bin-hadoop26an_zhuang.html Apache Spark1.1.0部署与开发环境搭建   Spark是Apache公司推出的一种基于Hadoop Distributed File System(HDFS)的并行计算架构。与MapReduce不同&am…

《大数据原理:复杂信息的准备、共享和分析》一一2.5 在标识符中嵌入信息:不推荐...

2.5 在标识符中嵌入信息&#xff1a;不推荐大多数标识符不是纯粹的随机数&#xff0c;它们通常含有一些可由熟悉标识系统的人解释的嵌入信息。例如&#xff0c;标识符中可以嵌入姓的前三个字母&#xff0c;同样&#xff0c;标识符中也可以嵌入出生年份的最后两位数字。标识符中…

python基础知识-列表,元组,字典

列表&#xff08;list&#xff09; 赋值方法&#xff1a; l [11,45,67,34,89,23] l list() 列表的方法&#xff1a; 1 #!/usr/bin/env python2 3 class list(object):4 """5 list() -> new empty list6 list(iterable) -> new list initial…

车站计算机联锁系统的仿真设计,车站计算机联锁仿真设计.doc

车站计算机联锁仿真设计2012 届 交通运输 学院专 业学 号 2008学生姓名指导教师完成日期 2012年 月日计算机联锁是保证车站内列车和调车作业安全&#xff0c;提高车站通过能力的一种信号设备。设计以沙盘模型为根据&#xff0c;练习制作联锁信号图表&#xff0c;使用Visual Bas…

如何解决机器学习中的数据不平衡问题?

在机器学习任务中&#xff0c;我们经常会遇到这种困扰&#xff1a;数据不平衡问题。 数据不平衡问题主要存在于有监督机器学习任务中。当遇到不平衡数据时&#xff0c;以总体分类准确率为学习目标的传统分类算法会过多地关注多数类&#xff0c;从而使得少数类样本的分类性能下降…

ubuntu每次登陆都用root账号登陆

sudo -s 进入 root 用户权限模式 vi /etc/lightdm/lightdm.conf [SeatDefaults] greeter-sessionunity-greeter user-sessionUbuntu greeter-show-manual-logintrue allow-guestfasle 重启后再登陆就会 直接用root登陆了 版权声明&#xff1a;本文为博主原创文章&#xff0c;未…

js-BOM

私有变量&#xff1a; 1、在一个实例上调用setName&#xff08;&#xff09;会影响所有的实例 BOM&#xff1a; 1、全局变量不能通过delete操作符删除&#xff0c;而直接在window对象上定义的属性可以 2、尝试访问为声明的变量会抛出错误&#xff0c;但通过查询window对象&…

计算机组成实验v代表什么,2014计算机组成原理实验指导V1.3.docx

文档介绍&#xff1a;实验一运算器组成实验实验目的熟悉Logisim软件平台。掌握运算器基本工作原理掌握运算溢出检测的原理和实现方法;理解有符号数和无符号数运算的区别;理解基于补码的加/减运算实现原理;熟悉运算器的数据传输通路。实验环境Logisim是一款数字电路模拟的教育软…

四大技巧轻松搞定云容器

云容器技术&#xff0c;作为传统虚拟化管理程序的一种替代品&#xff0c;正称霸着云市场。容器是轻量级的&#xff0c;并提供增强的便携性&#xff0c;允许应用在平台之间迁移&#xff0c;而不需要开发者重做或重新架构应用。但是&#xff0c;尽管其好处让开发人员感到惊叹&…

Android 图文混排 通过webview实现并实现点击图片

在一个开源项目看到是用的webview 实现的 1. 这是在asset中的一个模板html <html> <head> <title>News Detail</title> <meta name"viewport" content"widthdevice-width, minimum-scale0.5, initial-scale1.2, maximum-scale2.0…

h5engine造轮子

基于学习的造轮子&#xff0c;这是一个最简单&#xff0c;最基础的一个canvas渲染引擎&#xff0c;通过这个引擎架构&#xff0c;可以很快的学习canvas渲染模式&#xff01; 地址&#xff1a;https://github.com/RichLiu1023/h5engine 这是一个比较有意思的h5渲染引擎&#xff…

计算机硬件选型报价,组装电脑硬件该怎么选择?这几个硬件要舍得花钱,千万别买错了!...

原标题&#xff1a;组装电脑硬件该怎么选择&#xff1f;这几个硬件要舍得花钱&#xff0c;千万别买错了&#xff01;组装电脑是多硬件组合的产物&#xff0c;每一个硬件对于电脑的性能都是有影响的&#xff0c;影响的大小与电脑的硬件有直接关系&#xff0c;有些硬件就要舍得花…

2017 省赛选拨 想打架吗?算我一个!所有人,都过来!(3) 递推 斐波拉数列的应用...

想打架吗&#xff1f;算我一个&#xff01;所有人&#xff0c;都过来&#xff01;(3) Submit Page Summary Time Limit: 2 Sec Memory Limit: 128 Mb Submitted: 28 Solved: 9 Description 现在《炉石传说》这款卡牌游戏已经风靡全球。2015年加入环境的“…

UITableViewCell中cell重用机制导致内容重复的方法

UITableView继承自UIScrollview,是苹果为我们封装好的一个基于scroll的控件。上面主要是一个个的UITableViewCell,可以让UITableViewCell响应一些点击事件&#xff0c;也可以在UITableViewCell中加入UITextField或者UITextView等子视图&#xff0c;使得可以在cell上进行文字编辑…

高级会计师计算机考试中级,会计师需要计算机等级考试吗

尘伴考证达人06-19TA获得超过671个赞[color#000][font宋体][size3][alignleft]广东省高级会计师评审职称外语&#xff0c;执行《关于调整完善我省职称外语政策的通知》(粤人发〔2018〕120号)[/align][alignleft]三、报考职称外语考试的等级要求[b][size3](一)申报高教、科研、卫…