本实例已经将完整代码上传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}
这么好的资料,居然没人回复。 谢谢分享啦。
多谢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!=’<small>’ + posts[j].time + '</small>' p=posts[j].post
在回复里贴代码,小横线变成了小圆点,请大家复制代码时注意。
代码放在github上岂不是更好。。。
回复junxing, 已经把代码放在github了。但由于是新手,还没有搞清楚如何把图片和需要引用的jquery.js和bootstrap.js, 以及css文件上传到repositories,请赐教,谢谢!
本实例已经将完整代码上传github了,网址 https://github.com/tonyzhan/microblog 欢迎大家下载!
mark
求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/’ }) }));
执行/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 可以改版了哇哈~~~
楼主,网页载入时出现以下错误: Error at new JS_Parse_Error (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:189:18) at js_error (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:197:11) at croak (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:661:41) at token_error (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:665:9) at unexpected (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:671:9) at /Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:831:17 at /Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:704:24 at block_ (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:978:20) at /Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:951:25 at function_ (/Users/Janet/Node/microblog/node_modules/jade/node_modules/with/node_modules/uglify-js/lib/parse.js:956:15)
请问是什么样的原因造成的?望回复!mecil9@jiachongs.com qq:7187689
这个应该是Jade module 安装问题,请使用NPM install重新安装最新版本的jade
为什么会报
sunlifei[@sunlifei-V470](/user/sunlifei-V470):~/Documents/Learning Node/microblog$ NODE_ENV=production node cluster.js
/home/sunlifei/Documents/Learning Node/microblog/models/db.js:5
e.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_P
^
TypeError: Cannot read property 'DEFAULT_PORT' of undefined
at Object.<anonymous> (/home/sunlifei/Documents/Learning Node/microblog/models/db.js:5:74)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at Object.<anonymous> (/home/sunlifei/Documents/Learning Node/microblog/models/user.js:1:77)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
@tonyzhan 跑不起来,指点下
可以将DEFAULT_PORT 改为mongodb的端口,如 ‘27017’
@tonyzhan 你这个项目是启动 cluster.js ?
我问下怎么运行呢?我是在windows环境下!
大神,我下载下来运行报错了. D:\nodejs\microblog-master>node app D:\nodejs\microblog-master\models\db.js:5 e.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_P ^ TypeError: Cannot read property ‘DEFAULT_PORT’ of undefined at Object.<anonymous> (D:\nodejs\microblog-master\models\db.js:5:74) at Module._compile (module.js:460:26) at Object.Module._extensions…js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Module.require (module.js:365:17) at require (module.js:384:17) at Object.<anonymous> (D:\nodejs\microblog-master\models\user.js:1:77) at Module._compile (module.js:460:26) at Object.Module._extensions…js (module.js:478:10)
mark
看看别人的源码自己也学习。。
mark
@liuyonggit 应该是和LZ当时用的mongodb版本不一样
应该是settings.js吧?
mark
mark…
为什么中文的用户名登录时,在发表微博的时候会报错呢?
@CodeingShow 你好,请问楼主的项目下载下来应该如何连接数据库并运行起来呢(新人捂脸),我按照楼上的改了端口号啥的,但是还是报了好多错,求指导
iiiiiiiiiiiiiiiiiii