zoukankan      html  css  js  c++  java
  • 一个完整的基于Node.js web应用详解

    本博客停止更新,请访问新个人博客:owenchen.duapp.com

    前言

    这篇文章所使用的例子是基于《node.js开发指南》这本书中的例子和源代码来做的。express从2.x到3.x引入了非常大的变化,很多模块都被独立出去,并且调用的接口也发生了很大的变化,所以原有的代码在express3.x上是不能运行的。在尝试的过程中,做了很多迁移的工作,同时将一些模块进行了一定的分离和整合,使得整个项目更加具有结构性和可扩展性。

    这里首先要推荐一下《node.js开发指南》这本书,想学习和使用Node.js的同学,这确实是一本非常全面的介绍Node.js的书籍,看完了之后,结合书中的例子代码,基本可以使用Node进行web应用的开发。

    我这里主要结合这个例子,以自己的理解和对例子的修改讲述一下使用Node.js进行web开发的整个过程。 我就直接从代码开始讲,对于环境的搭建,npm包安装,模块引入等大家可以另外找一些文章,或者从《node.js开发指南》这本书的相应章节去了解。

    目录结构:

    首先贴出目录结构:

    从每个文件和文件夹的名字上,相信大家能看出他们各自的功能。

    • main.js是整个应用的启动文件
    • settings.js中存放着系统的配置信息
    • server.js是系统服务配置和创建的地方
    • db.js是与数据库相关的内容
    • models模块中中存放着模型类如User,Post等,类似于Java中的Entity
    • routes中是系统页面跳转和请求分发处理的模块
    • views是系统展现给用户的页面
    • daos中分装了所有对数据库的操作,熟悉J2EE的同学应该都理解这一层做的事情
    • web中是一些静态元素,如html,js,css,images
    • package.json中定义了系统需要的其他的第三方模块,如express,ejs等
    • node_modules中则是存放通过npm安装的第三方包的地方

    接下来会对这些部分分别做详细的介绍和分析。

    基于express和ejs的MVC

    在后台的各个模块中其实有一个基于express和ejs的MVC模式。对应的三个模块为:models(M)-views(V)-routes(C),具体会在后面讲解他们三者是如何工作的。

    express

    关于express,我在这里不做详细的介绍,大家可以上它的官网做比较详细的了解。简单的讲,就是一套在Node.js上创建web应用的框架。提供了包括服务创建、启动、会话、路由等接口和实现。一个最基本的基于express的服务代码如下:

    var express = require('express');
    var app = express(); //创建服务
    
    app.get('/', function(req, res){ //路由所有的到根目录的请求
      res.send('hello world');
    });
    
    app.listen(3000); //启动服务,监听3000端口

    ejs

    ejs: ejs是一种基于js的模版技术,即通过在html片段中插入js代码。在发送到客户端之前现在服务器端进行解析处理,动态设置一些字段或者添加一些节点。JSP就是一种基于java的模版语言。

    在最新的ejs中可以支持以html文件作为模版文件,并且引入了include机制,可以使用include语句来引入其他的页面内容。这点和jsp很像。

    一个基于ejs的html文件可以写成如下:

    <% include header.html %>
     <% if (!locals.user) { %>
      <div class="hero-unit">
        <h1>欢迎来到 Microblog</h1>
        <p>Microblog 是一个基于 Node.js 的微博系统。</p>
        <p>
          <a class="btn btn-primary btn-large" href="/login">登录</a>
          <a class="btn btn-large" href="/reg">立即注册</a>
        </p>
      </div>
    <% } else { %>
     <% include say.html %>
    <% } %>
    <% include footer.html %>

    在使用ejs的时候,可以在js代码中调用res.render('index',{user:"Owen"})方法。这个方法有两个参数,第一个为模版文件的名字,第二个为需要传入到模版中使用的参数。

    exports.index = function(req, res) {
         res.render('index', {
            title: '首页',
            posts: posts
        });
    };

    在最新的ejs中,加入了作用域的概念,在模版文件中不能直接引用变量名来访问变量,而需要使用locals.xxx来访问相应的变量。这样做是为了避免全局变量的污染和冲突。

    MVC

    在这个MVC模式中主要用到了express的route功能和ejs的模版机制。

    在models模块中定义一些模型模块如User,Post等,这些类似与java中的Pojo或者Entity类。定义了模型的一些属性和方法。这些属性与数据库的字段相对应。比如一个简单的User model的模块可以定义如下:

    function User(user) {
        this.name = user.name;
        this.password = user.password;
    };
    module.exports = User;

    routes中定义了请求分发处理的过程。比如到所有到根目录(/)的请求都经过一定的处理然后转发到index view中,到/login的请求应该返回login.html页面。这个与j2ee项目中的web.xml或者使用struts时的struts.xml类似。在node中,遵循代码即配置的原则,下面先看一下/routes/index.js模块中的code。

    var crypto = require('crypto');
    var User = require('../models/User');
    var Post = require('../models/Post');
    var user = require('./user');
    
    var that = exports;
    
    exports.index = function(req, res) {
        Post.get(null, function(err, posts) {
            if (err) {
                posts = [];
            }
            res.render('index', {
                title: '首页',
                posts: posts
            });
        });
    };
    
    exports.login = function(req, res) {
        res.render('login', {
            title: '用戶登入',
        });
    };
    
    module.exports = function(app) {
        app.get('/', that.index);
    
        app.get('/login', checkNotLogin);
        app.get('/login', that.login);
    
        app.post('/login', checkNotLogin);
        app.post('/login', that.doLogin);
    
        app.get('/reg', user.reg);
        ....
    };

    上面的代码片段定义了index函数用来处理所有的到根目录的请求,login函数处理到/login的get请求。 而到注册模块的(/reg)的请求,则被转发到user.reg方法中。在route/user模块中,我们可以定义跟user相关的一些处理函数,如:

    var crypto = require('crypto');
    var User = require('../models/User');
    var UserDao = require('../daos/UserDao');
    var PostDao = require('../dao/PostDao');
    
    exports.view = function(req, res) {
        UserDao.get(req.params.user, function(err, user) {
            if (!user) {
                req.flash('error', '用户不存在');
                return res.redirect('/');
            }
            PostDao.get(user.name, function(err, posts) {
                if (err) {
                    req.flash('error', err);
                    return res.redirect('/');
                }
                res.render('user', {
                    title: user.name,
                    posts: posts,
                });
            });
        });
    };
    
    exports.reg = function(req, res) {
        res.render('reg', {
            title: '用户注册',
        });
    };

    通过将处理函数的拆分和封装,可以很好的让这些route模块起到类似java中的servlet或者service的作用。

    Views中则是将要返回给客户端展示的内容,route中通过对model的处理,将处理结果或者model的内容通过ejs的方式植入到html页面中返回给客户端。

     routes->models->daos

    routes作为请求接收分发的模块,在接收到请求之后调用models中的接口处理数据,在models中通过daos中的数据库操作接口完成对数据库数据的查询活更改。这样三个层次各自的职责以及之间的依赖关系就非常明确。拿注册过程举例,典型的调用如下:

    routes/index.js
    app.post('/login', checkNotLogin);
    app.post('/login', user.doLogin);
    
    routes/user.js
    exports.doLogin = function(req, res) {
        //生成口令的散列值
        var md5 = crypto.createHash('md5');
        var password = md5.update(req.body.password).digest('base64');
    
        User.get(req.body.username, function(err, user) {
            if (!user) {
                req.flash('error', '用户不存在');
                return res.redirect('/login');
            }
            if (user.password != password) {
                req.flash('error', '密码错误');
                return res.redirect('/login');
            }
            req.session.user = user;
            req.flash('success', '登录成功');
            res.redirect('/');
        });
    };
    
    models/User.js
    var UserDao = require('../daos/UserDao');
    
    User.get = function get(username, callback) {
        UserDao.get(username, callback);
    };
    
    daos/UserDao.js
    exports.get = function get(username, callback) {
      mongodb.open(function(err, db) {
        if (err) {
          return callback(err);
        }
        db.collection('users', function(err, collection) {
          if (err) {
            mongodb.close();
            return callback(err);
          }
          collection.findOne({name: username}, function(err, doc) {
            mongodb.close();
            if (doc) {
              var user = new User(doc);
              callback(err, user);
            } else {
              callback(err, null);
            }
          });
        });
      });
    };

    服务配置和启动

    main.js是系统启动的文件,在终端使用:node main.js将启动整个web应用。 

    var server = require('./server');
    server.start();

    我的这个文件中代码很简单,我将服务的配置和创建过程移到了server.js中:

    var express = require('express');
    var ejs = require('ejs');
    var flash = require('connect-flash');
    var MongoStore = require('connect-mongo')(express);
    var settings = require('./settings');
    var routes = require('./routes');
    
    var app = express();
    app.configure(function() {
        console.log(__dirname);
        app.set('views', __dirname + '/views');
        // app.set('view engine', 'ejs');
        app.engine('.html', ejs.__express);
        app.set('view engine', 'html');
        app.use(express.bodyParser());
        app.use(flash());
        app.use(express.methodOverride());
        app.use(express.cookieParser());
        app.use(express.session({
            secret: settings.cookieSecret,
            store: new MongoStore({
                db: settings.db
            })
        }));
        app.use(function(req, res, next) {
            res.locals.error = req.flash('error').toString();
            res.locals.success = req.flash('success').toString();
            res.locals.user = req.session ? req.session.user : null;
            next();
        });
        app.use(app.router);
        routes(app);
        app.use(express.static(__dirname + '/web'));
    });
    
    app.configure('development', function() {
        app.use(express.errorHandler({
            dumpExceptions: true,
            showStack: true
        }));
    });
    
    app.configure('production', function() {
        app.use(express.errorHandler());
    });
    
    exports.start = function() {
        app.listen(settings.port);
        console.log("Express server listening on port %d in %s mode", settings.port, app.settings.env);
    }

    这里重点介绍几个关键的地方:

    app.engine('.html', ejs.__express);
    app.set('view engine', 'html');

    这两句设置让ejs将.html文件作为模版文件。

    app.use(flash());

    express3.0以后移除了req.flash()方法,所以只有通过使用'connect-flash'中间件模块才能继续使用req.flash()方法。

    app.use(function(req, res, next) {
      res.locals.error = req.flash('error').toString();
      res.locals.success = req.flash('success').toString();
      res.locals.user = req.session ? req.session.user : null;
      next();
    });

    由于express3.0移除了app.dynamicHelper()接口,所以要继续使用类似的功能,可以使用如上的代码,将flash或者session中的内容动态绑定到locals局部对象上。这样在模版文件中可以直接使用locals.xxx的方式来访问到这些变量。

    到这里项目中比较关键的代码部分都介绍完了,项目代码我放到了github上:https://github.com/owenXin/microblogByOwen

    我本地win7环境运行正常(记得起mongodb哦 )。这是我第一次在Node上做项目,只是初步设想的结构,欢迎大家一起交流,提供宝贵的意见和建议。

    本博客停止更新,请访问新个人博客:owenchen.duapp.com

  • 相关阅读:
    博世传感器调试笔记(一)----加速度传感器BMA253
    博世传感器调试笔记(二)加速度及陀螺仪传感器BMI160
    如何分区硬盘
    " " 与" " 区别
    SPI、I2C、UART、I2S、GPIO、SDIO、CAN
    nandflash,norflash,sdram,emmc,rom,ram等各种存储器识别
    AAC音频格式详解
    【转载】音频基础知识
    指针数组和数组指针的区别
    关于720p和1080p观看距离和效果
  • 原文地址:https://www.cnblogs.com/owenChen/p/2872360.html
Copyright © 2011-2022 走看看