[关闭]
@artman328 2015-03-25T07:48:01.000000Z 字数 7712 阅读 950

用 Node.js 和 Express.js 构建 Web 应用

web

作者:云南技师学院 陶冠华


第一部分 快速编写一个 Express 的 MVC 应用程序

一、什么是 Node.js ?

Node.js 不是一个 Javascript 脚本文件,而是一个用于开发 JavaScript 应用的平台。他使用了 Google 的 JavaScript V8 引擎,并采用了异步编程模式,使得用它开发的应用具有高效、高并发访问、交互实时等特点,是目前最热门的技术之一。

二、什么是 Express.js ?

Express.js 是 NodeJs 下的一个 MVC WEB 框架。

三、建立开发环境

3.1 安装 NodeJs

http://nodejs.org 下载适合您操作系统的安装包进行安装。
安装完毕,到控制台(或命令窗口)执行:

node -v

如果输出版本号,说明安装正确。

3.2 建立工作目录

在硬盘某处建立一个工作目录以保存软件项目,如:
D:\works\first_app

3.3 安装 express

NodeJS平台使用 npm 命令管理软件包及其依赖关系。为了管理软件项目,在软件项目所在目录执行:

npm init

并回答相关问题后,会在软件项目文件夹生成一个 package.json 的文件,用于描述软件项目的相关信息和第三方依赖包。

接着安装 express

npm install express --save

最后的 --save 表示把所安装的依赖包记录到 package.json 文件中。

四、建立应用主文件

在软件项目文件夹下建立名为 server.js 的文件(文件名可以随意),输入以下内容:

// 1、引入第三方依赖包
var express = require('express');

// 2、引入本应用的模块


// 3、生成应用程序对象(其实是一个 web 服务器)
var server = express(); 

// 4、配置服务器,可设置任意信息
server.set('version','0.0.1');
server.set('port', process.env.PORT || 8000);


// 5、加载中间件(middleware),是一段拦截请求的程序,可对请求进行
// 加工,或根据请求生成其它可用对象等...



// 6、配置请求路由
server.get('/',function(req,res){
    res.send("Hello, world!");
});  


// 7、捕获 404 错误并将错误向前传递
server.use(function(req, res, next) {
  var err = new Error('请求的资源不存在!');
  err.status = 404;
  next(err);
});

// 8、错误处理

// 8.1、开发环境下的错误处理
// 将输出详细错误信息
if (server.get('env') === 'development') {
  server.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.send('错误: ' + err.message);
  });
}

// 8.2、正常运行(生产)环境下的错误处理
// 不会输出详细信息,避免黑客利用
server.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.send('错误: ' +err.message);
});

// 9、启动服务器 
server.listen(server.get('port'),function(){
    console.log('服务器已启动,监听 ' + server.get('port') + ' 端口...');
});

保存文件,然后打开命令窗口(如果没有打开的话),进入软件项目所在文件夹,执行:

node server

应当能在命令窗口看到:

服务器已启动,监听 8000 端口...

的信息。

用浏览器打开以下网址:

127.0.0.1:8000

应当能够在浏览器里看到 “Hello, world!" 字样。

五、将路由移到一个独立的文件中

为避免主文件过长,我们把路由部分放到外部一个独立的文件中。

在软件项目文件夹下建立一个名为 routes.js 的文件,输入以下内容:

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

router.get('/',function(req,res){
    res.send('Hello, from the router!');
});

module.exports = router;

保存文件,然后在应用主文件 server.js 的第2节加入:

var router = require('./routes');

将应用主文件 server.js 的第 6 节内容删除,加入:

server.use(router);

保存文件,重新启动,用浏览器打开以下网址:

127.0.0.1:8000

应当能够在浏览器里看到 “Hello, from the router!" 字样。

六、将路由处理函数移到控制器中

为了合理组织应用程序,我们把路由处理函数移到控制器模块中。

在软件项目文件夹下创建一个名为 controllers 的文件夹,并在其中建立一个名为 site.js 的文件,输入以下内容:

function index(req,res){
    res.send('Hello, from the controller!');
}

module.exports.index = index;

保存文件,在 routes.js 文件的开头部分加入:

var siteCtrl = require('./controllers/site');

将:

router.get('/',function(req,res){
    res.send('Hello, world!');
});

替换为:

router.get('/',siteCtrl.index);

保存文件,重新启动主文件,用浏览器打开以下网址:

127.0.0.1:8000

应当能够在浏览器里看到 “Hello, from the controller!" 字样。

七、监视应用的修改并自动重启应用主程序

为避免每次修改都要重启应用主程序(服务器),我们可以安装一个监视程序帮我们在每次修改保存后自动重启服务器。

用以下命令安装监视程序:

npm install nodemon -g

-g表示将监视程序安装到全局,以后任何项目都可使用。

安装完毕后,用:

nodemon server

启动服务器,以后应用程序各部分修改保存后,都会自动重启我们的服务器。

八、把控制器中的直接发送响应改成发送视图

8.1 建立视图文件夹和视图文件

在软件项目文件夹下创建一个名为 views 的文件夹,在其中建立一个名为 index.ejs 的文件,输入以下内容:

<!doctype html>
<html>
    <head>
        <title>来自视图文件</title>
        <link rel="stylesheet" type="text/css" href="css/site.css">
    </head>
    <body>
        <h1>Hello, <%= name %>!</h1>
    </body>
</html>

8.2 安装视图引擎软件包

为了将视图文件和数据相结合生成完整的页面送回客户端,我们需要相应的视图引擎软件包,常用的有:jade, ejs, handlebars 等。在此我们使用 ejs。

使用下列命令安装 ejs 视图引擎:

npm install ejs --save

8.3 让服务器使用 ejs 视图引擎

在服务器主文件 server.js 的第1节加入:

var path = require('path')

(path 是 nodejs 的内置模块,不需要另外安装)然后在服务器主文件 server.js 的第4节内容中加入:

server.set('views', path.join(__dirname,'views'));
server.set('view enging', 'ejs');`

8.4 配置静态资源

在软件项目文件夹中创建一个名为 public 的文件夹,并在其中创建另一个名为 css 的文件夹,在 css 文件夹中创建一个名为 site.css 的文件,内容如下:

body{
    width: 960px;
    margin: 0 auto;
    background-color: #CCC;
}

在第5节加入以下内容:

server.use(express.static(path.join(__dirname,'public')));

(请求的资源会先到 public 文件夹下找,如果找到,直接送回客户端,否则往下传递请求)

8.5 修改控制器文件内容

将 controllers 文件夹中的 site.js 文件的内容修改如下:

function index(req,res){
    res.render('index',{name:'Tracy'});
}

module.exports.index = index;

保存文件,这时,服务器应当会自动重启。用浏览器打开以下网址:

127.0.0.1:8000

应当能够在浏览器里看到 “Hello, Tracy!" 字样。

九、建立和使用模型

在软件项目文件夹下建立一个名为 models 的文件夹,并在其中建立一个名为 user.js 的文件,内容如下:

var User = function(id,name){
    this.id = id;
    this.name = name;
}

module.exports.User = User;

然后在控制器文件 controllers/site.js 在头部,加上以下内容:

var User = require('../models/user').User;

将文件的其余内容修改如下:

function index(req,res){
    user = new User(1,'张三');
    res.render('index',{user:user});
}

module.exports.index = index;

在视图文件 views/index.ejs 中,把 标签中的内容更改如下:

<h1>Hello, <%= user.name %> </h1>

保存文件,这时,服务器应当会自动重启。用浏览器打开以下网址:

127.0.0.1:8000

应当能够在浏览器里看到 “Hello, 张三!" 的字样。

十、完整的目录结构和程序清单

1、目录结构

\first_app
    \controllers
        site.js
    \models
        user.js
    \node_modules
        \express
            ...
        \ejs
            ...
    \public
        \css
            site.css
    \views
        index.ejs
    package.js
    routes.js
    server.js

注意:node_modules 文件夹里是安装的第三方库。

2、文件内容

(1) controllers/site.js

var User = require('../models/user').User;

function index(req,res){
    user = new User(1,'张三');
    res.render('index',{greeting:'你好!',user:user});
}

module.exports.index = index;    

(2) models/user.js

var User = function(id,name){
    this.id = id;
    this.name = name;
}

module.exports.User = User;

(3) public/css/site.css

body{
    width: 960px;
    margin: 0 auto;
    background-color: #CCC;
}

(4) views/index.ejs

<!doctype html>
<html>
    <head>
        <title>来自视图文件</title>
        <link rel="stylesheet" type="text/css" href="css/site.css">
    </head>
    <body>
        <h1>Hello, <%= user.name%></h1>
    </body>
</html>

(5) routes.js

var express = require('express');
var router = express.Router();
var siteCtrl = require('./controllers/site');

router.get('/',siteCtrl.index);

module.exports = router;

(6) 主程序文件 server.js

// 1、引入第三方依赖包
var express = require('express');
var path = require('path');

// 2、引入本应用的模块
var router = require('./routes');


// 3、生成应用程序对象(其实是一个 web 服务器)
var server = express(); 

// 4、配置服务器,可设置任意信息
server.set('version','0.0.1');
server.set('port', process.env.PORT || 8000);
server.set('views',path.join(__dirname,'views'));
server.set('view engine','ejs');


// 5、加载中间件(middleware),是一段拦截请求的程序,可对请求进行
// 加工,或根据请求生成其它可用对象等...

server.use(express.static(path.join(__dirname,'public')));


// 6、配置请求路由
server.use(router);

// 7、捕获 404 错误并将错误向前传递
server.use(function(req, res, next) {
  var err = new Error('请求的资源不存在!');
  err.status = 404;
  next(err);
});

// 8、错误处理

// 8.1、开发环境下的错误处理
// 将输出详细错误信息
if (server.get('env') === 'development') {
  server.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.send('错误: ' + err.message);
  });
}

// 8.2、正常运行(生产)环境下的错误处理
// 不会输出详细信息,避免黑客利用
server.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.send('错误: ' +err.message);
});

// 9、启动服务器 
server.listen(server.get('port'),function(){
    console.log('服务器已启动,监听 ' + server.get('port') + ' 端口...');
});

第二部分 NodeJs 平台技术要点

一、异步编程模型

同步与异步是指程序(函数)的执行策略,在同步模式中,函数的执行会一直被等待,直到得到结果才往下执行程序。而在异步模式中,函数的执行是不会被等待的,如果用异步策略实现的函数按常规编写程序(即认定程序的执行是一句一句顺序执行得到结果的),将得不到预期的结果。以下举例说明。

以下是一段程序的伪代码:

a = getValue('file1.txt'); // 大约耗时0.5秒,得到 12
b = getValue('file2.txt'); // 大约耗时 0.5 秒,得到 30
c = a + b;
print "Total: " + c;

如果以上函数没有采用异步实现,执行第一步时等待大约0.5秒获得a值(12),执行第二步时又等待大约0.5秒得到b值(30),再顺序执行以下两句,程序将在大约1秒钟后输出:

Total: 42

但如果函数的实现采用了异步策略(函数执行不等待结果,执行完毕,触发一个事件,让一个等待结果的函数自动执行),即执行第一步时,不等待结果,执行第二句时也不等待结果,顺序执行到第三句时,a和b仍然没有得到结果(假定原来是空值),导致 c 的结果是空值,执行到第四句时,输出:

Total:

然后程序结束,没有达到预期的结果!

为了在异步执行时能够得到预期的结果,必须改写原有函数的实现(异步策略),并改写以上程序:

getValue('file1.txt',function(a){
    getValue('file2.txt',function(b){
        c = a + b;
        print "Total: " + c;
    });
});

以上函数被改写成适合异步调用的函数,

getValue(filename) => getValue(filename,func)

函数的最后一个参数是当函数执行完毕时要自动执行的函数(func)。

按以上方法实现异步执行,程序嵌套太多函数会使得程序不容易阅读,以后我们会学到,如何利用同步编程方法实现异步编程,以便编写和阅读。

综上所述,异步编程的基本结构是:
给函数1传入一个函数2(可以在外部定义,也可就地定义匿名函数)作为参数,当函数1完成时执行函数2。函数2也可以是一个异步函数(和函数1一样,也有一个函数作为参数),这样,可实现异步调用的多级嵌套,实现顺序执行异步函数。

NodeJS 中涉及输入输出(IO)的函数,都是异步函数。

二、NodeJS 程序模块

NodeJS 的程序由模块(Module)组成。模块就是一个 JavaScript 文件,一个模块可以引用(Require)另一个模块。每个模块有自己的私有作用域,除非选择导出(Export),变量和函数在外部是访问不到的。

如下列程序文件(util.js)为一个模块:

var myArr = [1,2,3,4,5];
var strArr = ['a','b','c'];

function add(num1,num2){
    return (num1 + num2);
}

var sub = function(num1,num2){
    return (num1 - num2);
}

module.exports.digArr = myArr;
module.exports.add = add;

在以下文件 (main.js) 中,引入以上模块:

var util = required('./util');
console.log(util.add(10,20)); //输出 30
console.log(util.digArr[0]); //输出 1
console.log(util.strArr[0]); //错误
console.log(util.sub(20,10)); //错误

执行以上文件,最后两行都会导致错误,因为 util.js 中的 sub 函数和 strArr 都没有被导出,从而访问不到。

在 NodeJS 程序中,require函数内部引用的模块名称不能带 .js 扩展名。不带路径信息的模块名是指通过 npm install 命令安装的第三方软件模块,默认在项目主文件夹中的 node_modules 文件夹中。引用本地模块,必须带路径信息,哪怕引用模块和被引用模块在同一文件夹中。

三、NodeJS 软件项目的建立

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注