zoukankan      html  css  js  c++  java
  • Nodejs之MEAN栈开发(一)---- 路由与控制器

    因为工作需要,最近再次学习了node,上一次学习node是2014年,纯粹是个人兴趣,学了入门之后没有运用,加上赶别的项目又不了了之。这次正好捡起来。废话不多说,这里的MEAN指的是MongodbExpressAngularNode。 通过整个项目逐步整合在一起。MEAN栈最大的特色不是运用了哪些框架或第三方,而是前后端都是一种语言,即JavaScript。早些年我也是对node抱着疑态度,觉得这个页面上操作dom的脚本语言,能扛得起后端那么多模块吗?但怀疑不防多了解一下,才决定写这个系列的文章。

    Mongodb做数据存储,Express是基于node的后端框架,Angular是前端框架,Node是后端运行环境。安装过程和node的特性就不讲了,网上一大把。开发环境是VS2013.安装了NTVS。node安装完后,有时候需要设置下环境变量。在cmd目录下输入 node -v 如果显示版本号,则说明安装正确了。

    起始工程

    在VS中新建项目,选择JavaScript-->Node.js,选择Express4的应用。

      

      为了避免一直Ctrl+C,安装nodemon,文件更新,它会自动重启,-g表示安装成全局。

    npm install nodemon -g

    修改routes文件夹下的index.js中的title为ReadingClub。然后用cmd切到工程目录,输入nodemon启动工程。 

     在浏览器里面访问lochost:3000 ,成功打开:

     先看routes文件夹下面的index.js ,这就是一个简单的路由,处理的路径为“/”,请求方式get,req代表的是request,res代表的response。

     

    render方法有两个参数,“index”,代表的是要渲染的视图模板名称,这里默认的视图引擎是jade,而后面{title:'ReadingClub'}就是传递到视图的数据模型。这里和Asp.net MVC 的return View() 有些相似,而这里的function就相当 Asp.net MVC中Controller的一个Action。View()默认是对应当前Action名称的视图。而render必须指定。

    res 也可以直接发回一个响应

    res.send('respond with a resource');

    建立Controllers

     不像Asp.net MVC有默认的路由规则,Express的路由需要一个个配置,不妨把Controller提出来。但在此之前,我们先修改一下目录。如下,建立app_server文件夹。里面分controllers、views和routes。可以把原来的views和routes直接移进去。

    在controllers文件夹中新建一个home.js,加入三个方法:index、books和about。

    module.exports.index = function(req, res) {
        res.render('index', { title: 'Index' });
    };
    
    module.exports.books = function(req, res) {
        res.render('books', { title: 'Books', });
    };
    
    module.exports.about = function (req, res) {
        res.render('about', { title: 'About' });
    };

    路由

    同样,在view文件夹中把index.jade复制两遍,修改为books.jade和about.jade. 然后我们修改routes下的index.js,运用Express框架自带的Router。

    var express = require('express');
    var router = express.Router();
    var homeController = require('../controllers/home');
    
    router.get('/', homeController.index);
    router.get('/about', homeController.about);
    router.get('/books', homeController.books);
    
    module.exports = router;

    这时候还是无法运行的,因为我们改变了目录结构还没有在app.js中重新设定。首先设置路由:

    var routes = require('./app_server/routes/index');
    app.use('/', routes);

    修改视图引擎的起始位置

    //app.set('views', path.join(__dirname, 'views'));
    app.set('views', path.join(__dirname, 'app_server', 'views'));

    __dirname代表的是根目录。然后再浏览器访问/books或者/about 。

     这样就分离了controller,请求通过路由抵达控制器,控制器将模型数据填充到对应的视图的模板.这就是我们熟悉的MVC模式。我们再看router.METHOD方法定义。

    router.METHOD(path, [callback, ...] callback)

    这里的METHOD指get,post,put和delete等。因为我们可以定义:

    router.get('/book/:bookId', homeController.detail);
    router.put('/book/:bookId', homeController.updateBook);
    router.delete('/book/:bookId', homeController.deleteBook);

    虽然路径都是一样,但是代表的是不同的用意,完全restful,:bookId表示是参数。

    同样支持正则匹配,会匹配类似于这样的‘GET /commits/71dbb9c’

    router.get(/^/commits/(w+)(?:..(w+))?$/, function(req, res){
      var from = req.params[0];
      var to = req.params[1] || 'HEAD';
      res.send('commit range ' + from + '..' + to);
    });

    如果每个请求都需要做某种处理,可以用all方法:

    router.all('*', requireAuthentication, loadUser);

    这等价于:

    router.all('*', requireAuthentication)
    router.all('*', loadUser);

    Asp.net MVC的路由每一个都需要设置名称,且不能重复出现,且匹配到之后就不再匹配,Express没有这个限制,匹配到之后只要没返回响应就会向下继续传递。相对而言,Express的Router更灵活一些。

    更多细节请参考官方API:http://www.expressjs.com.cn/4x/api.html#router

    接下来我们回顾下整个app.js。

    app.js

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    
    var routes = require('./app_server/routes/index');
    
    var app = express();
    
    // view engine setup
    app.set('views', path.join(__dirname, 'app_server', 'views'));
    app.set('view engine', 'jade');
    
    // uncomment after placing your favicon in /public
    //app.use(favicon(__dirname + '/public/favicon.ico'));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(require('stylus').middleware(path.join(__dirname, 'public')));
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', routes);
    
    // catch 404 and forward to error handler
    app.use(function (req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
    });
    
    // error handlers
    
    // development error handler
    // will print stacktrace
    if (app.get('env') === 'development') {
        app.use(function (err, req, res, next) {
            res.status(err.status || 500);
            res.render('error', {
                message: err.message,
                error: err
            });
        });
    }
    
    // production error handler
    // no stacktraces leaked to user
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: {}
        });
    });
    
    
    module.exports = app;
    View Code

    1.模块定义:

    首先看到很多的require的语法。

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    
    var routes = require('./app_server/routes/index');

    require表示应用一个模块,npm上已经有超过25万个包。这些能直接引用的模块,是已经安装在node_modules文件中。如果是自己定义的模块,比如routes 就要用相对路径。在node中模块分以下几类:

    • 核心模块,如http,fs,path等
    • .或..开始的相对路径文件模块
    • 以/开始的绝对路径文件模块
    • 非路径形式的文件模块,如自定义的模块。

    核心模块会优先加载,以相对或绝对路径加载的模块,require都会转为真实路径,将编译执行后的结果放到缓存中,这样二次加载就会更快。require能加载.js,.node.json的文件,其余扩展名都会被当.js文件载入。模块与文件是一一对应的,一个文件夹的模块就称作包,包通常是一些模块的集合。require是用来获取模块,而exports对象就是用来定义模块。

    module.exports.hello = function() {
    console.log('Hello.');
    };

    相当于是定义接口,给外部调用。而上面的路由就是把一整个对象封装到模块中。

    module.exports = router;

    在app.js中直接获取到整个路由对象:

    var routes = require('./app_server/routes/index');

    看到module.exports直接赋值router有点奇怪,会想不会覆盖掉其他的模块吗,但事实上在编译的过程中,node会对获取的JavaScript文件内容进行包装,等于是每个文件之间都进行了作用域的隔离。

    2.app.set

    app.js中用set方法设置了路由起始路径和视图引擎。 

    app.set('views', path.join(__dirname, 'app_server', 'views'));//这里我们修改了路径在app_server文件夹下
    app.set('view engine', 'jade');//默认的视图引擎是jade

    还可以设置路由是否忽略大小写,默认是不忽略。

    app.set('case sensitive routing',true)

    还可以设置环境变量是开发环境还是生产环境,更多的设置可以参考官方文档:http://www.expressjs.com.cn/4x/api.html#app.settings.table

    3.app.use

    use方法是用来注册一系列中间件,监听端口上的请求,中间件利用了尾触发机制,每个中间件传递请求对象,响应对象和尾触发函数,通过队列形成一个处理流。

     最简单的中间件形式:

    app.use(function (req, res, next) {
      console.log('Time: %d', Date.now());
      next();
    })

    看下各个中间件的作用:

    app.use(logger('dev')); //日志,在开发环境下用彩色输出响应状态,会显示请求方式,响应时间和大小。
    app.use(bodyParser.json());//解析json请求。
    app.use(bodyParser.urlencoded({ extended: false }));//解析form请求(含有key-value键值对),false表示value的类型是string或array,为true表示任意类型。
    app.use(cookieParser());//解析cookie
    app.use(require('stylus').middleware(path.join(__dirname, 'public')));//使用stylus做css预编译,并指定路径。
    app.use(express.static(path.join(__dirname, 'public')));//静态文件路径

    4.error

    我们看到在设置了路由之后,如果请求还没返回则认为页面没有找到,这个时候app抛出一个error。并继续往下传递

    app.use('/', routes);
    
    // catch 404 and forward to error handler
    app.use(function (req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
    });

    而接下来,对错误进行了处理

    // 开发环境错误处理
    // 会打印出错误堆栈
    if (app.get('env') === 'development') {
        app.use(function (err, req, res, next) {
            res.status(err.status || 500);//如果不是404就认为是内部错误
            res.render('error', {
                message: err.message,
                error: err
            });
        });
    }
    
    // 生产环境错误处理
    // no stacktraces leaked to user
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: {}
        });
    });

    检测到异常的时候,就渲染error模板。 接下来看下error模板,简单介绍下jade语法:

    extends layout  //相当于Asp.net MVC 设置Layout
    
    block content //相当于 Asp.net MVC RenderBody
      h1= message  //显示message
      h2= error.status  //显示错误状态
      pre #{error.stack} //错误堆栈

    这样就能处理404和500错误页面。

    小结:至此,整个默认工程已经介绍完,这一节通过Express框架建立一个基本的MVC工程,了解基本的请求和响应,node的基本模块和中间件;并初步设置了路由,建立起专门的controller;解读了app.js中的相关代码;下一节关注模型和视图。时至今日,node的开发环境已经很完善,从09年到现在这个技术已经走过了7年了,已经有很多书籍资料,国内的cnode社区很活跃。如果把技术比喻成股票的话,java,C#,PHP这些无疑是大盘白马股,学这样的技术风险小,不愁找不到工作。而node这样的就像创业板股票,你也许认为这有很大的泡沫,认为新的公司不过是炒概念,但他就是在快速增长着。

     源码:http://files.cnblogs.com/files/stoneniqiu/Reading.rar

    参考文章:

    http://www.tuicool.com/articles/emeuie 

    http://www.2cto.com/kf/201207/142885.html

    http://www.tuicool.com/articles/emeuie

    https://github.com/expressjs/body-parser

    书籍:《深入浅出nodejs》《Getting MEAN with Mongo, Express, Angular, and Node

  • 相关阅读:
    前端基础开发之HTML
    内置函数(二)
    html
    内置函数(一)
    二进制安装mysql
    .net Parallel并行使用
    MVC 枚举绑定 DropDownList
    MVC扩展Url.Action方法解决复杂对象参数问题
    Index.cshtml”处的视图必须派生自 WebViewPage 或 WebViewPage<TModel>。
    设置网站URL启动
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/5538109.html
Copyright © 2011-2022 走看看