精华 使用express4.x版和Jade模板重写《nodejs开发指南》微博实例
发布于 5个月前 作者 tonyzhan 5960 次浏览 最后一次编辑是 2个月前 来自 分享

本实例已经将完整代码上传github了,网址 https://github.com/tonyzhan/microblog 欢迎大家下载!

《nodejs开发指南》是一本好书,但微博开发实例已经过时了。express4.x发布了,Jade模板是express的默认模板。 学习了jade模板后发觉也没什么难的,而且html代码清爽了许多。 网上好多人说ejs模板怎么好,我却觉得仿佛回到了asp时代,凡是像microsoft的东西,我都不感冒。 我使用express4.x版和Jade模板重写《nodejs开发指南》微博实例,请大家参考,欢迎大家多提意见。

Installation 安装

$ npm install express

快速建立项目

$ npm install -g express-generator

创建项目目录

$ express /tmp/microblog && cd /tmp/microblog

安装中间件和依赖项

$ npm install

启动服务器, 这个有些特别,请注意:

$  cd /tmp/microblog
$ bin/www

##我的目录,敬请参考

microblog
--bin
  --www
--models
  --db.js
  --post.js
  --user.js
--node_modules
  --body-parser
  --connect-flash
  --connect-mongo
  --cookie-parser
  --debug
  --express
  --express-session
  --jade
  --mongodb
  --morgan
  --static-favicon
--public
  --images
  --javascripts
    --bootstrap.js
    --jquery.js
  --stylesheets
    --bootstrap.css
    --bootstrap-responsive.css
    --style.css
--routes
  --index.js
  --users.js //这个是express自带的,基本没用
--views 
  --error.jade //这个是express自带的,基本没用
  --index.jade
  --layout.jade
  --login.jade
  --posts.jade
  --reg.jade
  --say.jade
  --user.jade //这个是ejs模板需要用到的,在jade模板下发觉没有用,可以使用include直接放在index.jade就可以实现需求了
--app.js
--package.json
--setting.js

以下是源代码,就不说废话了:

app.js的代码:

var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
//var partials = require('express-partials');用jade模板,不能使用这个中间件
var session    = require('express-session');
var MongoStore = require('connect-mongo')(session);
var settings = require('./settings');
var flash = require('connect-flash');
var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//app.use(partials());

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());

//cookie解析的中间件
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(flash());

//提供session支持
app.use(session({
    secret: settings.cookieSecret,
    store: new MongoStore({
        db: settings.db,
    })
}));


app.use(function(req, res, next){
  console.log("app.usr local");
  res.locals.user = req.session.user;
  res.locals.post = req.session.post;
  var error = req.flash('error');
  res.locals.error = error.length ? error : null;
 
  var success = req.flash('success');
  res.locals.success = success.length ? success : null;
  next();
});


app.use('/', routes);
app.use('/users', users);


/// 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;

## index.js代码
var express = require('express');
var app = express();
var router = express.Router();
var crypto = require('crypto');
var User = require('../models/user.js');
var Post = require("../models/post.js");
/* GET home page. */

router.get('/', function(req, res) {
  Post.get(null, function(err, posts) {
  if (err) {
    posts = [];
  }
  res.render('index', {
    title: '首页',
    posts: posts,
    user : req.session.user,
            success : req.flash('success').toString(),
            error : req.flash('error').toString()
  });
  });
  //res.render('index', { title: '首页' });
});

router.get("/reg", checkNotLogin);
router.get("/reg",function(req,res) {
  res.render("reg",{
    title : "用户注册"
  });
});

router.get("/login", checkNotLogin);
router.get("/login",function(req,res) {
  res.render("login",{
    title:"用户登录",
  });
});

router.get("/logout", checkLogin);
router.get("/logout",function(req,res) {
  req.session.user = null;
  req.flash('success', '退出成功');
  res.redirect('/');
});

router.get("/user", function(req,res){
  res.render("user",{
    title: "用户页面",
  });
});

router.post("/login", checkNotLogin);
router.post("/login",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', req.session.user.name + '登录成功');
    
    res.redirect('/');
  });
});

router.post("/reg", checkNotLogin);
router.post("/reg", function(req, res) {
  console.log(req.body['password']);
  console.log(req.body['password-repeat']);
  if(req.body['password-repeat'] != req.body['password']){
    req.flash('error', '两次输入的密码不一致');
    return res.redirect('/reg');
  }  
  var md5 = crypto.createHash('md5');
  var password = md5.update(req.body.password).digest('base64');

  var newUser = new User({
    name: req.body.username,
    password: password,
  });
  //检查用户名是否已经存在
  User.get(newUser.name, function(err, user) {
    if (user) {
      err = 'Username already exists.';
    }
    if (err) {
  req.flash('error', err);
  return res.redirect('/reg');
    }

    newUser.save(function(err) {
  if (err) {
    req.flash('error', err);
    return res.redirect('/reg');
  }
  req.session.user = newUser;
  req.flash('success', req.session.user.name+'注册成功');
  res.redirect('/');
    });
  });  
});

function checkNotLogin(req, res, next) {
  if (req.session.user) {
    req.flash('error', '用户已经登录');
    return res.redirect('/');
  }
  next();
}
function checkLogin(req, res, next) {
  if (!req.session.user) {
    req.flash('error', '用户尚未登录');
    return res.redirect('/login');
  }
  next();
}

router.post("/post",checkLogin);
router.post("/post",function(req,res) {
  var currentUser = req.session.user;
  var post = new Post(currentUser.name, req.body.post);
  post.save(function(err) {
    if (err) {
      req.flash('error', err);
      return res.redirect('/');
    }
    req.flash('success', '发表成功');
    res.redirect('/u/' + currentUser.name);
  });
});

router.get("/u/:user",function(req,res) {
  User.get(req.params.user, function(err, user) {
    if (!user) {
      req.flash('error', '用户不存在');
      return res.redirect('/');
    }
    Post.get(user.name, function(err, posts) {
      if (err) {
        req.flash('error', err);
        return res.redirect('/');
      }
      res.render('user', {
        title: user.name,
        posts: posts
      });
    });
  });
});
module.exports = router;

##settings.js

module.exports = {
cookieSecret: 'microblogtony2014',
db: 'blog',
host: 'localhost',
};

package.json:

express-partials不需要装,这是ejs模板需要用到了。

#我装了并引用,jade模板就不工作了,费了我好长的时间去找问题。

{
  "name": "microblog",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "express": "~4.2.0",
    "static-favicon": "~1.0.0",
    "morgan": "~1.0.0",
    "cookie-parser": "~1.0.1",
    "body-parser": "~1.0.0",
    "debug": "~0.7.4",
    "jade": "~1.3.0",
    "mongodb": ">= 1.4.8",
    "connect-mongo": ">=0.1.7",
    "express-session": "~1.0.4",
    "connect-flash": "*"
  }
}

##db.js

var settings = require('../settings'),
    Db = require('mongodb').Db,
    Connection = require('mongodb').Connection,
    Server = require('mongodb').Server;
module.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_PORT, {}), {safe: true});

##post.js

var mongodb = require('./db');

function Post(username,post,time) {
  this.user = username;
  this.post = post;

  if(time) {
    this.time = time;
  } else {
    this.time = new Date();
  }
};

module.exports = Post;

Post.prototype.save = function save(callback) {

  var post = {
    user: this.user,
    post: this.post,
    time: this.time,
  };
  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }

    db.collection('posts', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }

      // collection.ensureIndex('user');

      collection.insert(post, {safe: true}, function(err, post) {
        mongodb.close();
        callback(err, post);
      });
    });
  });
};

Post.get = function get(username, callback) {
  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }

    db.collection('posts', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      var query = {};
      if (username) {
        query.user = username;
      }
      collection.find(query).sort({time: -1}).toArray(function(err, docs) {
        mongodb.close();
        if (err) {
          callback(err, null);
        }

        var posts = [];
        docs.forEach(function(doc, index) {
          var post = new Post(doc.user, doc.post, doc.time);
          posts.push(post);
        });
        callback(null, posts);
      });
    });
  });
};

##user.js

var mongodb = require('./db');

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

module.exports = User;

//存入Mongodb的文档
User.prototype.save = function save(callback) {
  var user = {
    name: this.name,
    password: this.password,
  };

  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }
    //读取users集合
    db.collection('users', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      // 为name属性添加索引
      // collection.ensureIndex('name', {unique: true});

      //写入user文档
      collection.insert(user, {safe: true}, function(err, user) {
        mongodb.close();
        callback(err, user);
      });
    });
  });
}

User.get = function get(username, callback) {
  mongodb.open(function(err, db) {
    if (err) {
      return callback(err);
    }
    //读取users集合
    db.collection('users', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      //查找name属性为username的文档
      collection.findOne({name: username}, function(err, doc) {
        mongodb.close();
        if (doc) {
          //封装文档为User对象
          var user = new User(doc);
          callback(err, user);
        } else {
          callback(err, null);
        }
      });
    });
  });
};

##layout.jade

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/bootstrap.css')
    link(rel='stylesheet', href='/stylesheets/style.css')
    link(rel='stylesheet', href='/stylesheets/bootstrap-responsive.css')
    script(src='/javascripts/jquery.js')
    script(src='/javascripts/bootstrap.js')  
  body
    div.navbar.navbar-fixed-top
      div.narbar-inner
        div.container
          a(class='btn btn-navbar' data-toggle='collapse' data-target='.nav-collapse') span.icon-bar span.icon-bar span.icon-bar
          a(class='brand' href='/') Microblog
          div.nav-collapse
            ul.nav
              li.active 
                a(href='/') 首页
              - if (!user)
                li
                  a(href='/login') 登录
                li
                  a(href='/reg') 注册
              - else
                li
                  a(href='/logout') 退出
    div#container.container
      - if (success)
        div.alert.alert-success= success
      - if (error)
        div.alert.alert-error= error      
      block content
    div#footer.footer
      !='<hr />'
      p
        a(href='http://www.hotelanywhere.cn' target='_blank') Tony Zhang [@2014](/user/2014) All Right Reserved

##index.jade

extends layout
block content
   - if (!user)
     div.hero-unit
       h1 Welcome to Microblog
       p Microblog是一个基于Node.js的微博系统。
       p
         a(class='btn btn-primary btn-large' href='/login') 登录
         a(class='btn btn-large' href='/reg') 注册
   - else
     include ./say.jade
   include ./posts.jade

##reg.jade

extends layout

block content
 
  form(class='form-horizontal' method='post')
    fieldset
      legend 用户注册
      div.control-group
        label(class='control-label' for='username') 用户名
        div.controls
          input(type='text' class='input-xlarge' id='username' name='username')
          p(class='help-block') 你的账户名称,用于登录和显示
      div.control-group
        label(class='control-label' for='password') 密码
        div.controls
          input(type='password' class='input-xlarge' id='password' name='password')
      div.control-group
        label(class='control-label' for='password') 请再次输入密码
        div.controls
          input(type='password' class='input-xlarge' id='password-repeat' name='password-repeat')
      div.form-actions
        button(type='submit' class='btn btn-primary') 注册

##login.jade

extends layout

block content

  form( class='form-horizontal' method='post')
    fieldset
      legend 用户登录
        div.control-group
          label(class='control-label' for='username') 用户名
          div.controls
            input(type='text' class='input-xlarge' id='username' name='username')
        div.control-group
          label(class='control-label' for='username') 密码
          div.controls
            input(type='password' class='input-xlarge' id='password' name='password')
        div.form-actions
          button(type='submit' class='btn btn-primary') 登录

##say.jade

form( class='well form-inline center' method='post' action='/post' style='text-align:center;')
    input(type='text' class='span8' id='post' name='post')
    button(type='submit' class='btn btn-success') 发言

##posts.jade

- for (var i = 3; i < posts.length+3; i = i + 3)
  div.row
  - for(var j = i-3; j < i && j < posts.length; j++)
    div.span4
    h2
       a(href='/u/'+posts[j].user)=posts[j].user 
        !='说'

    p!='<small>' + posts[j].time + '</small>'
    p=posts[j].post

##user.jade: 以前我以为不需这个模板,后来才发现这个模板的重要,是查看每个用户的发言的必要的模板

extends layout
block content
  include narbar.jade
  div.jumbotron
    div.container
      - if (user)
        include ./say.jade
  div.container
    include ./posts.jade
    include footer.jade

##error.jade

extends layout

block content
  h1= message
  h2= error.status
  pre #{error.stack}
28 回复

这么好的资料,居然没人回复。 谢谢分享啦。

多谢jayself的鼓励! posts.jade做了更新,解决了之前没有做到的3个发言在一行的样式问题。由于jade的语法,我使用了2重循环解决了实现了div的嵌套。对比ejs模板,jade在有条件嵌套div上有些支持不够。 以下为posts.jade的代码,供大家参考。如果有更好的解决方法,请分享给我,多谢了。

  • for (var i = 3; i < posts.length+3; i = i + 3) div.row
    • for(var j = i-3; j < i && j < posts.length; j++) div.span4 h2 a(href=’/u/’+posts[j].user)=posts[j].user !=’说’ p!=’' + posts[j].time + '' p=posts[j].post

在回复里贴代码,小横线变成了小圆点,请大家复制代码时注意。

代码放在github上岂不是更好。。。

回复junxing, 已经把代码放在github了。但由于是新手,还没有搞清楚如何把图片和需要引用的jquery.js和bootstrap.js, 以及css文件上传到repositories,请赐教,谢谢!

本实例已经将完整代码上传github了,网址 https://github.com/tonyzhan/microblog 欢迎大家下载!

求QQ 一起交流一下

不错,正在找jade的项目,谢谢啦

我的QQ号是2411551649, 欢迎与我交流

不错,终于有份源码学学……

setttings.js

module.exports = {
cookieSecret: 'microblogtony2014',
db: 'blog',
host: '192.168.1.104',
};

配置已经改了,还是提示 Error: Error connecting to database: failed to connect to [127.0.0.1:27017]

TypeError: Property ‘next’ of object #<IncomingMessage> is not a function

回yakczh, 能把你的代码发给我吗? 我可以看看问题在哪。

@tonyzhan 没有改代码,就是改了配置文件,我本地没有mongodb,用的是局域网服务器上的mongodb你换个host试下

回yakczh,“ Error: Error connecting to database: failed to connect to [127.0.0.1:27017]”说明还是在连接本地的mongodb, 不是连接192.168.1.104. 你再查查局域网服务器的mangodb连接有没有设置IP限制。 实在不行,就在本地安装mangodb吧

@yakczh 阅读一下connect-mongo.js的构造方法 //提供session支持 app.use(session({ secret: settings.cookieSecret, store: new MongoStore({ db: settings.db, url: ‘mongodb://’+settings.host+’:27017/’ }) }));

@tonyzhan

执行/u/:user的方法时报错,我用console打出程序执行顺序的时候,发现已经执行到 res.render('user’, { title: user.name, posts: posts }); 请教一下问题出在哪里

http.js:689 throw new Error(‘Can’t set headers after they are sent.’); ^ Error: Can’t set headers after they are sent. at ServerResponse.OutgoingMessage.setHeader (http.js:689:11) at IncomingMessage.write (/root/Documents/proj/microblog/node_modules/express/node_modules/finalhandler/index.js:107:9) at IncomingMessage.g (events.js:180:16) at IncomingMessage.emit (events.js:117:20) at _stream_readable.js:943:16 at process._tickCallback (node.js:419:13)

还是得感谢楼主终于找到一个靠谱的express4+jade的教程了

谢谢这么无私的奉献

@jacobsuyu 这个问题已经解决,是由于user.jade模板的代码错误,请看我上面修改的代码,或者重新从github上下载我更新的代码。抱歉,最近很忙,没有登录cnode社区。

实在看不下去, 帮楼主整个标记了一遍…

clone嘞 嘞 謝謝

简直好东西啊 - -

好东西!慢慢学习!

真心不错,学习中

@cunzhi 可以改版了哇哈~~~

回到顶部