初学node.js有感三
Posted 精心出精品
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初学node.js有感三相关的知识,希望对你有一定的参考价值。
WebStorm下的node.js
一、回顾与继续
在前面,我们知道了node.js的基本框架和思路,在这些原生环境下我们对node.js的设计思想有了比较深刻的认识,并且具有了编写大型程序的能力了,但是程序的代码可能会比较拉杂,因此,我们就需要用到现有的框架了,在我们的社会上,不需要造太多的轮子,而需要在更高层次上去设计‘车’,因此使用别人做好的模板去完成自己的业务是一个比较节省时间并且有创造性的事情,在node.js中我们有太多的轮子,比如说express框架,为我们提供了封装好的很多做事情的接口,我们可以方便的进行组装和搭配,比如ejs、jade这些现有的模板引擎,为我们提供了很多可扩展的能力,在这个基础上我们可以最大限度的发挥自己的创造性,去完成自己业务。
二、Express框架
Express框架是后台的Node框架,所以和jQuery、zepto、yui、bootstrap都不一个东西。Express在后台的受欢迎的程度,和jQuery一样,就是企业的事实上的标准。原生Node开发,会发现有很多问题。比如:
- 呈递静态页面很不方便,需要处理每个HTTP请求,还要考虑304问题
- 路由处理代码不直观清晰,需要写很多正则表达式和字符串函数
- 不能集中精力写业务,要考虑很多其他的东西
Express的哲学是在你的想法和服务器之间充当薄薄的一层。这并不意味着他不够健壮,或者没有足够的有用特性,而是尽量少干预你,让你充分表达自己的思想,同时提供一些有用的东西。同样的,我们还是使用npm来安装该框架:--save参数,表示自动修改package.json文件,自动添加依赖项。
1 npm install --save express
用express框架写一个最简单的程序:
1 var express=require("express");
2
3 var app=new express();
4 app.get("/",function(req,res){
5 res.send("success!");
6 });
7 app.get(/\\/student\\/([\\d]{10})/,function (req,res) {
8 res.send("student info:"+req.params[0]);
9 })
10 app.listen(3100);
在这里我们首先引用了该框架,其次通过new关键字创建了该对象,之后,我们对于首页的访问返回成功,对于路径名为‘/student/十位数字’的访问,我们通过正则表达式返回了相应的学号,在这里之所以可以使用req的params[0]返回相应的数字,是因为我们在正则表达式中用()对相应的地方进行了选中,同样的,我们还可以对更长的路径名进行这样的过滤,道理是一样的,同样的我们可以看到get方法可以对用户访问的路径进行解析,当然post主要是对表单进行处理的,另外在这里除了用正则表达式之外我们还可以使用 : 来对我们想要表达的文字进行表示,比如这里我们就可以写为“/student/:id”,那么我们就可以得到id的值了,只要再使用正则表达式判断位数就可以了。
1 app.get("/student/:id",function(req,res){
2 var id = req.params["id"];
3 var reg= /^[\\d]{6}$/; //正则验证
4 if(reg.test(id)){
5 res.send(id);
6 }else{
7 res.send("请检查格式");
8 }
9 });
运行结果如下:
在这里我们还可以使用use方法来暴露静态文件,也叫静态文件伺服能力:
app.use(express.static("./public"));
这样我们就可以在网址中输入public文件夹下的任何路径而不需要输入public,很好的隐藏了一些重要的内容、迷惑了黑客的视线。并且假如public文件夹下有一个a.html文件,我们可以直接用127.0.0.1:3100/a.html来访问,更强大的是如果public文件夹下有一个index.html文件,则直接输入127.0.0.1:3100即可以访问。
a、get用法:
GET请求的参数在URL中,在原生Node中,需要使用url模块来识别参数字符串。在Express中,不需要使用url模块了。可以直接使用req.query对象。
当用get请求访问一个网址的时候,做什么事情:
app.get("网址",function(req,res){
});
这里的网址,不分大小写,也就是说,路由是
app.get("/AAb",function(req,res){
res.send("你好");
});
实际上小写的访问也行。
所有的GET参数,? 后面的都已经被忽略。 锚点#也被忽略,路由到/a , 实际/a?id=2&sex=nan 也能被处理。
表单可以自己提交到自己上。
app.get("/",function(req,res){
res.render("form");
});
适合进行 RESTful路由设计。简单说,就是一个路径,但是http method不同,对这个页面的使用也不同。
/student/345345
get 读取学生信息
add 添加学生信息
delete 删除学生信息
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title></title>
6 </head>
7 <body>
8 <form action="#" method="post">
9 <input type="text" name="name"/>
10 <input type="text" name="age"/>
11 <input type="submit"/>
12 </form>
13 </body>
14 </html>
b、post用法:
POST请求在express中不能直接获得,必须使用body-parser模块。使用后,将可以用req.body得到参数。但是如果表单中含有文件上传,那么还是需要使用formidable模块。当用post访问一个网址的时候可以使用下面的格式:
app.post("网址",function(req,res){
});
app.post("/",function(req,res){
//将数据添加进入数据库
res.send("成功");
});
c、all的用法:
如果想处理这个网址的任何method的请求,那么写all
app.all("/",function(){
});
三、中间件
我们输入的网址就像一个水流,中间件就像是过滤水流的大网,水流中的某些东西流经某一个地方被阻挡住的话就不会再向下面流去了,因此网的大小(中间件)的前后顺序非常重要,如果get、post回调函数中,没有next参数,那么就匹配上第一个路由,就不会往下匹配了。如果想往下匹配的话,那么需要写next()。
1 app.get("/",function(req,res,next){
2 console.log("1");
3 next();
4 });
5 app.get("/",function(req,res){
6 console.log("2");
7 });
让我们看一个例子,下面两个路由,感觉没有关系:
1 app.get("/:username/:id",function(req,res){
2 console.log("1");
3 res.send("用户信息" + req.params.username);
4 });
5 app.get("/admin/login",function(req,res){
6 console.log("2");
7 res.send("管理员登录");
8 });
但是实际上冲突了,因为admin可以当做用户名 login可以当做id。
解决方法1:交换位置。
也就是说,express中所有的路由(中间件)的顺序至关重要。
匹配上第一个,就不会往下匹配了。 具体的往上写,抽象的往下写。
1 app.get("/admin/login",function(req,res){
2 console.log("2");
3 res.send("管理员登录");
4 });
5 app.get("/:username/:id",function(req,res){
6 console.log("1");
7 res.send("用户信息" + req.params.username);
8 });
解决方法2: 使用next()
app.get("/:username/:id",function(req,res,next){
var username = req.params.username;
//检索数据库,如果username不存在,那么next()
if(检索数据库){
console.log("1");
res.send("用户信息");
}else{
next();
}
});
app.get("/admin/login",function(req,res){
console.log("2");
res.send("管理员登录");
});
路由get、post这些东西,就是中间件,中间件讲究顺序,匹配上第一个之后,就不会往后匹配了。next函数才能够继续往后匹配。
app.use()也是一个中间件。与get、post不同的是,他的网址不是精确匹配的。而是能够有小文件夹拓展的。
比如网址: http://127.0.0.1:3000/admin/aa/bb/cc/dd
1 app.use("/admin",function(req,res){
2 res.write(req.originalUrl + "\\n"); // /admin/aa/bb/cc/dd
3 res.write(req.baseUrl + "\\n"); // /admin
4 res.write(req.path + "\\n"); // /aa/bb/cc/dd
5 res.end("你好");
6 });
当不写路径的时候,实际上就相当于"/",就是所有网址
app.use(function(req,res,next){
console.log(new Date());
next();
});
app.use()就给了我们增加一些特定功能的便利场所。
大多数情况下,渲染内容用res.render(),将会根据views中的模板文件进行渲染。如果不想使用views文件夹,想自己设置文件夹名字,那么
app.set("views","aaaa");
如果想写一个快速测试页,当然可以使用res.send()。这个函数将根据内容,自动帮我们设置了Content-Type头部和200状态码。send()只能用一次,和end一样。和end不一样在于能够自动设置MIME类型。如果想使用不同的状态码,可以:
1 res.status(404).send(\'Sorry, we cannot find that!\');
如果想使用不同的Content-Type,可以:
res.set(\'Content-Type\', \'text/html\');
四、小小相册中的大大智慧
讲了这么多理论性的东西,我们需要实战来检验自己的掌握程度,因此,在这里我们使用node.js为服务器,制作一个相册管理器,我们可以在上面上传相片,也可以查看不同文件夹中的相片,在这里我们使用MVC模型来进行项目的布局、设计和实现!
程序的功能:
1、B/S模式,使用node.js作为服务器,浏览器为客户端,可以远程访问,至少保持局域网内的可访问性。
2、具有图片的上传能力,可以将特定的图片上传到特定的文件夹中,对上传图片的大小有限制,上传的图片具有统一格式的命名。
3、具有高效路由能力,可以通过静态路由功能找到相应文件夹下的图片并且显示出来。
4、需要用到bootstrap、express框架、jQuery支持、ejs模板引擎、MVC等多种技术。
首先让我们看一下工程的目录:
其中node_modules中至少包含express、ejs、silly-datetime、formidable这些基本的模块。工程采用了MVC的模式,具有很强的可扩展性,在app.js是整个工程的配置和启动,在controller中是对app.js中的命令的一种解析和执行,对于不同的请求采用不同的方法进行响应,在models中是对数据的一种管理,比如读取文件的目录或者文件夹的目录等,并且返回这些数据给controller,而controller拿着这些数据交给views去渲染,并且显示相应的页面,可以说后端的呈递完全就是对数据的操作和显示,这些数据包括简单的数值、变量、对象、数组、字节流、字符流、多媒体数据等,而前端主要用来进行渲染和展现,在这里我们还是使用ejs模板引擎将得到的数据进行渲染和显示,并且使用了bootstrap技术和JQuery技术来增加美化程度,更快更好地完善我们的功能。uploads静态文件夹用来保存上传过来的图片,temp文件夹用来间接地呈递我们上传的图片,作为中转。public静态文件夹主要存放一下浏览器可以直接访问到的数据,比如css、HTML、图片、bootstrap等数据和文件,这就是文件的结构了。
4.1、app.js文件
1 var express=require("express");
2 var app=express();
3 var router=require("./controller/router.js")
4 app.set("view engine","ejs");
5
6 app.use(express.static("./public"));
7 app.use(express.static("./uploads"));
8
9 app.get("/",router.showIndex);
10
11 app.get("/up",router.showUp);
12 app.post("/up",router.doPost);
13
14 app.get("/:albumName",router.showAlbum);
15
16 app.use(function (req,res) {
17 res.render("err");
18 });
19 app.listen(3100);
这是整个工程的指导文件,对于每一个请求都将通过该文件进行分发和处理,可以说是核心枢纽,实现使用了ejs模板引擎,默认文件夹views为将要渲染的文件夹。其次暴露了两个文件夹作为静态文件夹,提供了对这两个文件夹下的所有文件的路由能力。然后对于首页访问命令,直接通过controller层进行细节的处理,通过MVC框架来呈现首页,然后是对于文件上传的两个界面,首先我们需要跳转到上传文件界面,这个时候使用get命令即可完成,并且返回填写信息界面,因此走了一遍MVC,其次是填写完表单需要提交的时候,我们需要使用post命令来提交表单,并且通过formidable来处理上传的图片信息。然后是对于图片文件夹的访问,我们对于不同的文件夹呈现不同的图片列表,最后是对于任意上面处理不了的信息,我们返回错误页面,当然也是通过ejs渲染之后来呈现。然后令程序监听3000以后的某个端口,避免冲突。
4.2、controller下面的router.js文件
1 var file=require("../models/file.js");
2 var formidable = require(\'formidable\');
3 var sd = require("silly-datetime");
4 var path = require("path");
5 var fs=require("fs");
6 //首页信息
7 exports.showIndex=function(req,res){
8 file.getAllAlbums(function (err,allAlbums) {
9 if(err){
10 res.render("err");
11 return;
12 }
13 res.render("index",
14 {"albums":allAlbums}
15 );
16 })
17 }
18 //显示图片列表
19 exports.showAlbum=function(req,res){
20 var albumName=req.params.albumName;
21 file.getAllImagesByAlbumname(albumName,function(err,imageArray){
22 if(err){
23 res.render("err");
24 return;
25 }
26 res.render("album",
27 {
28 "albumName":albumName, "images":imageArray
29 }
30 );
31 });
32 }
33 //显示填写上传文件ejs
34 exports.showUp=function(req,res){
35 file.getAllAlbums(function(err,albums)
36 {
37 res.render("up", {
38 "allAlbums": albums
39 });
40 });
41 }
42 //提交上传的文件并处理,持久化
43 exports.doPost=function(req,res){
44 var form = new formidable.IncomingForm();
45 form.uploadDir = "./temp/";
46 form.parse(req, function (err, fields, files) {
47 console.log(fields);
48 console.log(files);
49 var size=parseInt(files.picture.size);
50 // if(size>1024*1024)
51 // {
52 // res.send("图片尺寸应小于1M");
53 // //删除图片
54 // fs.unlink(files.picture.path);
55 // console.log("删除成功!")
56 // return;
57 // }
58 var ttt = sd.format(new Date(), \'YYYYMMDDHHmmss\');
59 var ran = parseInt(Math.random() * 89999 + 10000);
60 var extname = path.extname(files.picture.name);
61 var oldpath = __dirname + "/../" + files.picture.path;
62 var newpath = __dirname + "/../uploads/" +fields.folder+"/"+ ttt + ran + extname;
63 console.log(oldpath);
64 console.log(newpath);
65 fs.rename(oldpath, newpath, function (err) {
66 if (err) {
67 throw Error("改名失败");
68 console.log("失败");
69 return;
70 }
71 res.render("success");
72 console.log("成功");
73 return;
74 });
75 });
76 }
这个文件可以说是承上启下,首先分派一个任务交由models层来完成,得到相应的数据,然后拿着这些数据通过res.render()来渲染并且显示,并且暴露自己,可以让app.js访问到。在这里,我们需要注意异步编程的特点,一定要用callback函数来处理那些需要读写文件系统或者其他I/O设备的操作,不然将会出现异常和错误。
4.3、models文件夹下面的file.js文件
1 var fs = require("fs");
2 //返回所有文件夹列表
3 exports.getAllAlbums=function(callback){
4 fs.readdir("./uploads",function (err,files) {
5 if(err){
6 callback("读取文件夹失败!",null);
7 return;
8 }
9 var allAlbums=[];
10 (function iterator(i) {
11 if(i==files.length)
12 {
13 callback (null,allAlbums);
14 return;
15 }
16 fs.stat("./uploads/"+files[i],function (err,stats) {
17 if(err){
18 callback("解析文件夹失败!"+files[i],null);
19 return;
20 }
21 if(stats.isDirectory())
22 {
23 allAlbums.push(files[i]);
24 }
25 iterator(i+1)
26 });
27 })(0);
28 });
29 }
30 //根据文件夹的名字来找到该文件夹下的所有图片文件并且返回
31 exports.getAllImagesByAlbumname =function(albumname,callback){
32 fs.readdir("./uploads/"+albumname,function (err,files) {
33 if(err){
34 callback("读取文件夹失败!",null);
35 return;
36 }
37 var allImages=[];
38 (function iterator(i) {
39 if(i==files.length)
40 {
41 callback (null,allImages);
42 console.log(allImages);
43 return;
44 }
45 fs.stat("./uploads/"+albumname+"/"+files[i],function (err,stats) {
46 if(err){
47 callback("解析文件失败"+files[i],null);
48 return;
49 }
50 if(stats.isFile())
51 {
52 allImages.push(files[i]);
53 }
54 iterator(i+1)
55 });
56 })(0);
57 });
58 }
其实我们仔细思考一下就能明白,对文件的数据进行读,我们只有这两种操作,一种是返回所有文件夹列表,另一种是根据文件夹的名字来找到该文件夹下的所有图片文件并且返回,这两种操作就是完全精细的,适用于我们项目需求的原子操作,可以用来复用。在这里我们使用了iterator来保证同步执行。值得注意的是,如果我们文件读取失败或者文件夹读取失败,会提示isFile()或者isDirectory()未定义,这个时候,我们需要做的就是仔细看一下文件或者文件夹是否因为路径的原因而没有读取成功。这点在编程中十分重要。还有crtl+alt+I(i),注意这个地方一定是I,而不是L,可以对我们选中的(crtl+A)代码进行整理,这些编程技巧十分重要!!!!!!
4.4、views文件夹下面的ejs文件
完成了这些我们就需要关注一下前端的实现了,在这里我们通过ejs来进行渲染,同样的,我们借鉴了bootstrap中的模板,在www.bootcss.com网站中我们可以清楚的找到属于我们的组件和实例,并且搭建最基本的前端界面,本程序就是从上面借鉴的!我们从这个网站中下载bootstrap,然后解压之后,放到我们的public文件夹下面去,然后在该网站中找到“起步”,从中下载最简单的使用代码,然后在“全局CSS样式”和“组件”中,我们根据自己的业务要求选择适合自己使用的控件进行布局,具体的细节这里就略去不提了,最重要的是bootstrap中对jQuery有依赖,因此我们需要从网上下载jQuery的函数库,就是一个文件而已,然后放到bootstrap文件夹中的js文件夹下,这样我们就完成了准备工作!!!!!!
准备完成了之后,让我们看一下ejs代码,下面的是index.ejs的代码:
1 <!DOCTYPE html>
2 <html lang="zh-CN">
3 <head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
8 <title>小小相册</title>
9 <link href="css/bootstrap.min.css" rel="stylesheet">
10 <style type="text/css">
11 .row h4{
12 text-align:center;
13 }
14 </style>
15 </head>
16 <body>
17
18
19 <nav class="navbar navbar-default">
20 <div class="container-fluid">
21 <!-- Brand and toggle get grouped for better mobile display -->
22 <div class="navbar-header">
23 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
24 <span class="sr-only">Toggle navigation</span>
25 <span class="icon-bar"></span>
26 <span class="icon-bar"></span>
27 <span class="icon-bar"></span>
28 </button>
29 <a class="navbar-brand" href="#">小小相册</a>
30 </div>
31
32 <!-- Collect the nav links, forms, and other content for toggling -->
33 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
34 <ul class="nav navbar-nav">
35 <li class="active"><a href="#">全部相册 <span class="sr-only">(current)</span></a></li>
36 <li><a href="/up">上传</a></li>
37 </ul>
38 </div>
39 </div>
40 </nav>
41 <div class="container">
42 <div class="row">
43 <% for (var i=0;i<albums.length;i++){%>
44 <div class="col-xs-6 col-md-3">
45 <a href="<%=albums[i]%>" class="thumbnail">
46 <img src="images/folder.jpg" alt="">
47 </a>
48 <h4><%=albums[i]%></h4>
49 </div>
50 <%}%>
51 </div>
52 </div>
53
54 <script src="js/jquery-1.11.3.js"></script>
55 <script src="js/bootstrap.min.js"></script>
56 </body>
57 </html>
同样的,其他的ejs文件渲染如下:
album.ejs:
1 <!DOCTYPE html>
2 <html lang="zh-CN">
3 <head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
8 <title>小小相册</title>
9 <link href="/css/bootstrap.min.css" rel="stylesheet">
10 <style type="text/css">
11 .row h4{
12 text-align:center;
13 }
14 </style>
15 </head>
16 <body>
17
18 <nav class="navbar navbar-default">
19 <div class="container-fluid">
20 <!-- Brand and toggle get grouped for better mobile display -->
21 <div class="navbar-header">
22 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
23 <span class="sr-only">Toggle navigation</span>
24 <span class="icon-bar"></span>
25 <span class="icon-bar"></span>
26 <span class="icon-bar"></span>
27 </button>
28 <a class="navbar-brand" href="#">小小相册</a>
29 </div>
30
初学Node.js