---恢复内容开始---
Node.js+Express+MongoDB+Bootstrap开发微博网站的过程如下:
1.建立微博网站基本网站结构
1.1 为了学习简便,采用和html结构相似的ejs作为模板引擎。
1 express -e myBlog
1.2 进入项目目录,执行npm install 命令,安装项目所需要的依赖。
1.3 运行npm start 命令,打开所需浏览器访问localhost:3000,可以看到如下结果,说明项目目录初始化成功。
2.搭建多人博客
2.1 功能分析
本实例实现的是微博的基本功能,包括用户注册、登陆、发表微博、显示全部(个人)微博和退出微博几个功能。
2.2 设计路由规划
1 /: 主页 2 /login: 登录页 3 /reg: 注册页 4 /post: 发表文章 5 /logout: 登出
注意:登陆和注册只能在用户未登录状态下使用,发表微博和退出只能在用户登录状态下使用,首页和用户首页在登陆和未登录状态下显示不同的内容。通过会话(session)管理用户登录状态。用户登录、注册、发表成功以及登出后都会回到主页。
2.3 修改 index.js 如下:
var express = require('express'); var router = express.Router(); //创建模块化安装路径的处理程序。 //主页路由 router.get('/', function(req, res) { res.render('index', { title: '主页' }); }); //注册页路由 router.get('/reg', function(req, res) { res.render('reg', { title: '注册'}); }); router.post('/reg', function(req, res) { }); //登录页路由 router.get('/login', function(req, res) { res.render('login', { title: '登录'}); }); router.post('/login', function(req, res) { }); //发表文章也路由 router.get('/post', function(req, res) { res.render('post', { title: '发表'}); }); router.post('/post', function(req, res) { }); //登出的路由 router.get('/logout', function(req, res) { }) module.exports = router;
2.4 界面设计,使用Bootstrap前端框架布局,根据功能以及路由规划,相应的界面和对应的模板引擎为:
首页:index.ejs
用户首页:user.ejs
登陆页面:login.ejs
注册页面:reg.ejs
公用页面:header.ejs(顶部导航条)、alert.ejs(顶部下方错误信息显示)、foote.ejsr(底部显示)、say.ejs(发布微博)、posts.ejs(按行按列显示已发布的微博)
2.4.1 使用bower安装Bootstrap、jQuery框架
bower install bootstrap jquery
3.微博功能的实现
3.1 访问mongodb数据库
3.1.1 通过淘宝镜像安装一下模块
npm install mongodb --save -d --registry=https://registry.npm.taobao.org npm install express-session --save -d --registry=https://registry.npm.taobao.org npm install connect-mongo --save -d --registry=https://registry.npm.taobao.org npm install connect-flash --save -d --registry=https://registry.npm.taobao.org
3.1.2 在项目根目录下建立settings.js文件,用于保存该项目的配置信息,比如数据库的连接信息。其中,db是数据库的名称,host是数据库的地址,cookieSecret是用来对cookie进行加密,port为数据库的端口号。
module.exports = { cookieSecret: 'myblog', db: 'blog', host: 'localhost', port: 27017 }
3.1.3 然后在项目的根目录下新建models文件夹,并且在model文件夹下新建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, settings.port), { safe: true });
3.2 会话支持(session)
var settings = require('./settings'); var session = require('express-session'); var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: settings.cookieSecret,
cookie: {maxAge: 1000*60*60*24*30},
key: settings.db,
store: new MongoStore({
url: 'mongodb://localhost/blog'
})
}));
3.3 页面通知(connect-flash)
//通过淘宝镜像安装connect-flash模块
npm install connect-flash --save -d --registry=https://registry.npm.taobao.org
//然后修改app.js,添加
var flash = require('connect-flash');
//最后应用flash功能
app.use(flash())
3.4 响应注册
3.4.1 在models文件夹下新建user.js,添加如下代码:
1 var mongodb = require('./db'); 2 3 //User构造函数,用于创建对象 4 function User(user) { 5 this.name = user.name; 6 this.password = user.password; 7 this.email = user.email; 8 }; 9 10 //输出User对象 11 module.exports = User; 12 13 //存储用户信息到mongodb数据库 14 User.prototype.save = function(callback) { 15 //要存入数据库的文档 16 var user = { //用户信息 17 name: this.name, 18 password: this.password, 19 email: this.email 20 }; 21 22 //打开数据库 23 mongodb.open(function(err, db) { 24 if(err) { 25 return callback(err); //错误,返回err信息 26 } 27 //读取users集合 28 db.collection('users', function(err, collection) { 29 if(err) { 30 mongodb.close(); 31 return callback(err); //错误,返回err信息 32 } 33 34 // 为name属性添加索引 35 // collection.ensureIndex('name', {unique: true}); 36 37 //将user用户数据插入users集合 38 collection.insert(user, {safe: true}, function(err, user) { 39 mongodb.close(); 40 if(err) { 41 return callback(err); //错误,返回err信息 42 } 43 callback(null, user[0]); //成功,err为null,并返回存储后的用户文档 44 }); 45 }); 46 }); 47 }; 48 49 //从数据库中读取用户信息 50 User.get = function(name, callback) { 51 //打开数据库 52 mongodb.open(function(err, db) { 53 if(err) { 54 return callback(err); //错误,返回err信息 55 } 56 57 //读取users集合 58 db.collection('users', function(err, collection) { 59 if(err) { 60 mongodb.close(); 61 return callback(err); //错误,返回err信息 62 } 63 //查找用户名(name键)值为name的一个文档 64 collection.findOne({name: name}, function(err, user) { 65 mongodb.close(); 66 if(err) { 67 return callback(err); //失败!返回err信息 68 } 69 callback(null, user); //成功,返回查询的用户信息 70 }) 71 }) 72 }) 73 }
3.4.2 然后打开index.js,在前面添加如下代码:
var crypto = require('crypto'); User = require('../models/user.js');
3.4.3 其次,修改index.js中的app.post('/reg/)如下所示:
1 router.post('/reg', function(req, res) { 2 var name = req.body.name, 3 password = req.body.password, 4 password_re = req.body['password-repeat']; 5 6 //检测用户两次输入的密码是否一致 7 if(password != password_re) { 8 req.flash('error', '两次输入的密码不一致!'); 9 return res.redirect('/reg'); //返回注册页 10 } 11 12 //生成密码的md5值 13 var md5 = crypto.createHash('md5'), 14 password = md5.update(req.body.password).digest('hex'); 15 //用新注册用户信息对象实例化User对象,用于存储新注册用户和判断注册用户是否存在 16 var newUser = new User({ 17 name: req.body.name, 18 password: password, 19 email: req.body.email 20 }); 21 //检查用户名是否已经存在 22 User.get(newUser.name, function(err, user) { 23 if(user) { 24 req.flash('error', '用户名已存在!'); 25 return res.redirect('/reg'); //返回注册页面 26 } 27 //如果不存在则新增用户 28 newUser.save(function(err, user) { 29 if(err) { 30 req.flash('error', err); //保存错误信息,用于界面显示 31 return res.redirect('/reg'); //注册失败返回注册页面 32 } 33 req.session.user = user; //将用户信息存入session 34 req.flash('success', '恭喜你,注册成功!'); 35 res.redirect('/'); //注册成功后返回主页 36 }); 37 }); 38 });
3.4.4 修改header.ejs,让导航条在已登录和未登录状态下显示不同的内容:
1 <% if(user) { %> 2 <li><a href="/post" title='发表'>发表</a></li> 3 <li><a href="/logout" title='登出'>登出</a></li> 4 <% } else { %> 5 <li><a href="/login" title='登录'>登录</a></li> 6 <li><a href="/reg" title='注册'>注册</a></li> 7 <% } %>
3.4.5 为了在页面显示注册(或登陆)成功或失败信息,添加如下代码:
1 <!--显示成功或错误信息--> 2 <% if(success){ %> 3 <div class="alert alert-success"> 4 <%= success %> 5 </div> 6 <% } %> 7 <% if(error){ %> 8 <div class="alert alert-error"> 9 <%= error %> 10 </div> 11 <% } %>
3.5 登录与登出响应
3.5.1实现用户登录功能
1 router.post('/login', function(req, res) { 2 //生成密码的md5值 3 var md5 = crypto.createHash('md5'), 4 password = md5.update(req.body.password).digest('hex'); 5 //检查用户是否存在 6 User.get(req.body.name, function(err, user) { 7 if(!user) { 8 req.flash('error', '用户名不存在!'); 9 return res.redirect('/login'); //用户不存在则跳转到登录页 10 } 11 //检查密码是否一致 12 if(user.password != password) { 13 req.flash('error', '密码错误!'); 14 return res.redirect('/login'); //密码错误则跳转到登录页 15 } 16 //用户名密码均匹配后,将用户信息存入session 17 req.session.user = user; 18 req.flash('success', '登录成功!'); 19 res.redirect('/'); //登录成功后跳转到主页 20 }); 21 });
3.5.2 实现用户登出响应
1 router.get('/logout', function(req, res) { 2 //通过复制为null,丢掉session中的用户信息,实现用户的退出。 3 req.session.user = null; 4 req.flash('success', '登出成功!'); 5 res.redirect('/'); //登出成功后跳转到主页 6 });
3.6 页面权限控制
为了实现登陆和注册功能只对未登录的用户有效;发布和退出功能只对已登录的用户有效。如何实现页面权限控制呢?我们可以把用户登录状态检查放到路由中间件 中,在每个路径前增加路由中间件,通过调用next()函数转移控制权,即可实现页面权限控制。因此在index.js中添加checkNotLogin 和checkLogin函数来检测是否登陆,并通过next()转移控制权,检测到未登录则跳转到登录页,检测到已登录则跳转到前一个页。
1 function checkLogin(req, res, next) { 2 if(!req.session.user) { 3 req.flash('error', '您还没有登录!'); 4 res.redirect('/login'); 5 } 6 next(); //控制权转移:当不同路由规则向同一路径提交请求时,在通常情况下,请求总是被第一条路由规则捕获, 7 // 后面的路由规则将会被忽略,为了可以访问同一路径的多个路由规则,使用next()实现控制权转移。 8 } 9 10 function checkNotLogin(req, res, next) { 11 if(req.session.user) { 12 req.flash('error', '您已登录!'); 13 res.redirect('back'); //返回之前的页面 14 } 15 next(); 16 }
3.7 为了维护用户状态和flash的通知功能,给每一个ejs模板传入了以下三个值:
user: req.session.user success: req.flash('success').toString() error: req.flash('error').toString()
3.8 发表文章功能实现
3.8.1 发表页的页面设计
1 <div class="container"> 2 <form role="form" method="post"> 3 <h2>发表</h2> 4 <hr/> 5 <div class="form-group"> 6 <label for="title" class="control-label">标题:</label> 7 <input type="text" class="form-control" name="title" placeholder="请输入标题名称" required> 8 </div> 9 <div class="form-group"> 10 <label for="name" class="control-label">正文:</label> 11 <textarea class="form-control" name="post" rows="10" cols="10" required></textarea> 12 </div> 13 <div class="form-group"> 14 <button type="submit" class="btn btn-primary">发表</button> 15 </div> 16 </form> 17 </div>
3.8.2 文章模型
为了实现发布文章功能,首先创建Post对象,在models文件夹下创建post.js文件,该文件功能与user.js功能类似,用于存储新发布的文章及查询全部或指定用户的文章,post.js代码如下:
1 var mongodb = require('./db'); 2 3 function Post(name, title, post) { 4 this.name = name; 5 this.title = title; 6 this.post = post; 7 } 8 9 module.exports = Post; 10 11 //存储一篇文章及其相关信息 12 Post.prototype.save = function(callback) { 13 var date = new Date(); 14 //存储各种时间格式,方便以后扩展 15 var time = { 16 date : date, 17 year: date.getFullYear(), 18 month: date.getFullYear() + "-" + (date.getMonth() + 1), 19 day: date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(), 20 minute: date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 21 date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) 22 } 23 //要存入数据库的文档 24 var post = { 25 name: this.name, 26 time: time, 27 title: this.title, 28 post: this.post 29 }; 30 //打开数据库 31 mongodb.open(function(err, db) { 32 if(err) { 33 return callback(err); 34 } 35 //读取posts集合 36 db.collection('posts', function(err, collection) { 37 if(err) { 38 mongodb.close(); 39 return callback(err); 40 } 41 42 //为user属性添加索引 43 //collection.ensureIndex('user'); 44 45 //将文档插入到posts集合 46 collection.insert(post, {safe: true}, function(err) { 47 mongodb.close(); 48 if(err) { 49 return callback(err); //失败,返回err数据 50 } 51 callback(null); //返回err为空,即是null 52 }); 53 }); 54 }); 55 }; 56 57 Post.get = function(name, callback) { 58 //打开数据库 59 mongodb.open(function(err, db) { 60 if(err) { 61 return callback(err); 62 } 63 //读取posts集合 64 db.collection("posts", function(err, collection) { 65 if(err) { 66 mongodb.close(); 67 return callback(err); 68 } 69 //查找name属性为name的文章记录,如果name为null则查找全部记录 70 var query = {}; 71 if(name) { 72 query.name = name; 73 } 74 //根据query对象来查询文章,并按照时间排序 75 collection.find(query).sort({time: -1}).toArray(function(err, docs) { 76 mongodb.close(); 77 if(err) { 78 return callback(err); //失败,返回err信息 79 } 80 callback(null, docs); //成功,以数组的形式返回查询的结果 81 }); 82 }); 83 }); 84 };
3.8.3 发表响应
打开index.js,在User=require('../models/user.js')后添加一行代码:
Post = require('../models/post.js');
然后修改router.post('/post')的代码如下面所示:
1 router.post('/post', checkLogin); 2 router.post('/post', function(req, res) { 3 var currentUser = req.session.user, 4 post = new Post(currentUser.name, req.body.title, req.body.post); 5 6 post.save(function(err) { 7 if(err) { 8 req.flash('error', err); 9 return res.redirect('/'); 10 } 11 req.flash('success', '发布成功!'); 12 res.redirect('/'); //发表成功即跳转到首页 13 }); 14 });
其次,需要修改index.ejs,让主页显示发表过的文章及其相关信息。打开index.ejs,修改如下所示:
1 <!-- 发表的文章内容 --> 2 <div class="list-group"> 3 <% posts.forEach(function(post, index) { %> 4 <div class="list-group-item"> 5 <h4><a href="#"><%= post.title %></a></h4> 6 <p class="list-group-item-text" style="padding: 10px 0;"> 7 <%- post.post %> 8 </p> 9 <p class="info"> 10 <a href="#"><%= post.name %></a> 发布于: <%= post.time.minute %> 11 <span class='glyphicon glyphicon-comment' style="padding:0 10px;">评论(0)</span> 12 <span class="glyphicon glyphicon-share-alt">阅读(0)</span> 13 </p> 14 </div> 15 <% }); %>
最后,打开index.js,修改router.get('/')代码如下所示:
1 router.get('/', function(req, res) { 2 Post.get(null, function(err, posts) { 3 if(err) { 4 posts = []; 5 } 6 res.render('index', { 7 title: '主页', 8 user: req.session.user, 9 posts: posts, 10 success: req.flash('success').toString(), 11 error: req.flash('error').toString() 12 }); 13 }); 14 });
---恢复内容结束---