利用Express实现一个简单的博客

这篇教程,我们将利用Express实现一个简单的博客,如果你还没使用过Express,可以阅读这篇教程


搭建多人博客

功能分析

搭建一个简单的具有多人注册、登录、发表文章、退出功能的博客。

设计目标

未登录:主页左侧导航显示 home、login、register,右侧显示已发表的文章、发表日期及作者。

登录后,主页左侧导航显示 home,post,logout,右侧显示已发表的文章、发表日期及作者。

用户登录、注册、发表成功以及登出后都返回到主页。

未登录:

主页:

10012614_ZWMt.jpg

登录页:

10012614_THQW.jpg

注册页:

10012615_vSS7.jpg
登录后:

主页:

10012615_BuYn.jpg

发表页:

10012615_Ee27.jpg

注意:没有退出登录页,当点击 LOGOUT 后,退出登录并返回到主页。

路由规划

我们已经把设计的构想图贴出来了,接下来的任务就是完成路由规划了。路由规划,或者说控制器规划是整个网站的骨架部分,因为它处于整个架构的枢纽位置,相当于各个接口的粘合剂,所以应该优先考虑。

虽然原来的 app.jsroutes/index.js 实现了路由的功能,但是它把所有的路由功能代码都放在了 app.js 文件里,随着时间的推移 app.js 文件会变得臃肿难以维护,这也违背了代码模块化的思想,所以我们把实现路由功能的代码都放在 routs/index.js 里。官方给出的写法是在 app.js 中实现了简单的路由分配,然后再去 index.js 中找到对应的路由函数,最终实现路由功能。我们不妨把路由控制器和实现路由功能的函数都放在 index.js 里,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('./routes/index');

var app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

routes(app);

app.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + app.get('port'));
});

修改 index.js 如下:

module.exports = function(app) {
  app.get('/', function (req, res) {
    res.render('index', { title: 'Express' });
  });
};

现在,再运行你的 app,你会发现主页毫无二致。这里我们在 routes/index.js 中通过 module.exports 导出了一个函数接口,在 app.js 中通过 require 加载了 index.js 然后通过 routes(app) 调用了 index.js 导出的函数。

根据构思的结构图,我们可以做出如下路由规划:

/ : 首页
/login : 用户登录
/reg : 用户注册
/post : 发表文章
/logout : 退出登录

我们要求 /login/reg 只能是未登录的用户访问,而 /post/logout 只能是已登录的用户访问。左侧导航列表则针对已登录和未登录的用户显示不同的内容。

接下来正式修改路由 index.js 文件,如下:

module.exports = function(app) {

  app.get('/', function (req, res) {
    res.render('index', { title: '主页' });
  });

  app.get('/reg', function (req, res) {
    res.render('reg', { title: '注册' });
  });


  app.post('/reg', function (reg, res) {

  });

  app.get('/login', function (req, res) {
      res.render('login', { title: '登录' });
  });

  app.post('/login', function (req, res) {

  });

  app.get('/post', function (req, res) {
      res.render('post', { title: '发表' });
  });

  app.post('/post', function (req, res) {

  });

  app.get('/logout', function (req, res) {

  });

};

如何针对已登录和未登录的用户显示不同的内容呢?或者说如何潘盾用户是否已经登录了呢?我们通过引入会话(session)机制记录用户登录状态,还要访问数据库来保存和读取用户信息。下面我们将学习如何使用数据库。

使用数据库
MongoDB 简介

MongoDB 是一个基于分布式文件存储的 NoSQL(非关系型数据库)的一种,由C++语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 支持的数据结构非常松散,是类似 json 的 bjson 格式,因此可以存储比较复杂的数据类型。MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库表单查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB 没有关系型数据库中行和表的概念,不过有类似的文档和集合的概念。文档是 MongoDB 最基本的单位,每个文档都会以唯一的 _id 标识,文档的属性为 key/value 的键值对形式,文档内可以嵌套另一个文档,因此可以存储比较复杂的数据类型。集合是许多文档的总和,一个数据库可以有多个集合,一个集合可以有多个文档。

下面是一个 MongoDB 文档的示例:

{ 
  "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),
  "name" : "nswbmw",
  "age" : 22,
  "email" : [ "xxx@126.com", "xxx@gmail.com" ],
  "family" : {
    "mother" : { ... },
    "father" : { ... },
    "sister : {
      "name" : "miaomiao",
      "age" : 27,
      "email" : "xxx@163.com",
      "family" : {
        "mother" : { ... },
        "father" : { ... },
        "brother : { ... },
        "husband" : { ... },
        "son" : { ... }
      }
    }
  }
}

更多知识,可以查阅这里

安装MongoDB

安装 MongoDB 很简单,去官网下载对应系统的 MongoDB 压缩包即可。解压后将文件夹重命名为 mongodb,并在mongodb文件夹里新建 blog 文件夹作为我们博客内容的存储目录。进入到bin目录下,运行:

mongod --dbpath ../blog/

以上命令的意思是设置blog文件夹作为我们工程的存储目录并启动数据库。

链接MongoDB

数据库虽然安装并启动成功了,但我们需要链接数据库后才能使用数据库。怎么才能在 Node.js 中使用 MongoDB 呢?我们使用官方提供的 node-mongodb-native 驱动模块,打开 package.json ,在 dependencies 中添加一行:

"mongodb": "1.4.15"

然后运行 npm install 更新依赖的模块,稍等片刻后 mongodb 模块就下载并安装完成了。

其实,还有另一种安装方法,你可以直接运行 npm install mongodb --save 命令进行安装,该命令会直接完成上面的两个步骤。

接下来在工程的根目录中创建 settings.js 文件,用于保存该博客工程的配置信息,比如数据库的连接信息。我们将数据库命名为 blog,因为数据库服务器在本地,所以 settings.js 文件的内容如下:

module.exports = {
    cookieSecret: 'myblog',
    db: 'blog',
    host: 'localhost',
    port: 27017
}

其中,db 是数据库的名称,host 是数据库的地址,port 是数据库的端口号,cookieSecret 用于 Cookie 加密与数据库无关,我们留作后用。

接下来,在根目录下面新建 models 文件夹,并在 models 文件夹下新建 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});

其中,通过 new Db(settings.db, new Server(settings.host, settings.port), {safe: true}); 设置数据库名、数据库地址和数据库端口创建了一个数据库链接实例,并通过 module.exports 导出该实例。这样,我们就可以通过 require 这个文件来对数据库进行读写了。

打开 app.js ,在 var routes = require('./routes/index'); 下添加:

var settings = require('./settings');
会话支持

会话是一种持久的网络协议,用于完成服务器和客户端之间的一些交互行为。会话是一个比连接粒度更大的概念, 一次会话可能包含多次连接,每次连接都被认为是会话的一次操作。在网络应用开发中,有必要实现会话以帮助用户交互。例如网上购物的场景,用户浏览了多个页面,购买了一些物品,这些请求在多次连接中完成。许多应用层网络协议都是由会话支持的,如 FTP、Telnet 等,而 HTTP 协议是无状态的,本身不支持会话,因此在没有额外手段的帮助下,前面场景中服务器不知道用户购买了什么。

为了在无状态的 HTTP 协议之上实现会话,Cookie 诞生了。Cookie 是一些存储在客户端的信息,每次连接的时候由浏览器向服务器递交,服务器也向浏览器发起存储 Cookie 的请求,依靠这样的手段服务器可以识别客户端。我们通常意义上的 HTTP 会话功能就是这样实现的。具体来说,浏览器首次向服务器发起请求时,服务器生成一个唯一标识符并发送给客户端浏览器,浏览器将这个唯一标识符存储在 Cookie 中,以后每次再发起请求,客户端浏览器都会向服务器传送这个唯一标识符,服务器通过这个唯一标识符来识别用户。 对于开发者来说,我们无须关心浏览器端的存储,需要关注的仅仅是如何通过这个唯一标识符来识别用户。很多服务端脚本语言都有会话功能,如 PHP,把每个唯一标识符存储到文件中。

——《Node.js开发指南》

express 也实现了会话中间件,默认情况下是把用户信息存储在内存中,但我们既然已经有了 MongoDB,不妨把会话信息存储在数据库中,便于持久维护。为了使用这一功能,我们需要借助 express-sessionconnect-mongo 这两个第三方中间件,在 package.json 中添加:

"express-session": "1.9.1",
"connect-mongo": "0.4.1"

注意:如果报 "error setting ttl index on collection: sessions" 错误,把 “mongodb” & “connect-mongo” 版本号更新到最新。

运行 npm install 命令安装模块,打开 app.js ,添加以下代码:

var session = require('express-session');
var MongoStore = require('connect-mongo')(session);

app.use(session({
  secret: settings.cookieSecret,
  key: settings.db,
  resave: true,
  saveUninitialized: false,
  cookie: {maxAge:1000*60*60*24*30}, //30days
  store: new MongoStore({
    db: settings.db,
    host: settings.host,
    port: settings.port,
    url:'mongodb://localhost/blog' //要加一个url,
  })
}));

使用 express-sessionconnect-mongo 模块实现了将会话信息存储到 mongodb 中。secret 用来防止篡改 cookiekey 的值为 cookie 的名字,通过设置 cookiemaxAge 值设定 cookie 的生存期,这里我们设置 cookie 的生存期为30天,设置它的 store 参数为 MongoStore 实例,把会话信息存储到数据库中,以避免丢失。在后面的小节中,我们可以通过 req.session 获取当前用户的会话对象,获取用户的相关信息。

注册和登录

我们已经准备好了数据库访问和会话的相关信息,接下来我们可以完成用户注册和登录功能。

页面设计

首先我们来完成主页、登录页和注册页的页面设计。

修改 view/index.ejs 如下:

<%- include header %>
这是主页
<%- include footer %>

views 文件夹下新建 header.ejs,添加如下代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Blog</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>

<header>
<h1><%= title %></h1>
</header>

<nav>
<span><a title="主页" href="/">home</a></span>
<span><a title="登录" href="/login">login</a></span>
<span><a title="注册" href="/reg">register</a></span>
</nav>

<article>

新建 footer.ejs ,添加如下代码:

</article>
</body>
</html>

修改 public/stylesheets/style.css 如下:

/* inspired by http://yihui.name/cn/ */
*{padding:0;margin:0;}
body{width:600px;margin:2em auto;padding:0 2em;font-size:14px;font-family:"Microsoft YaHei";}
p{line-height:24px;margin:1em 0;}
header{padding:.5em 0;border-bottom:1px solid #cccccc;}
nav{float:left;font-family:"Microsoft YaHei";font-size:1.1em;text-transform:uppercase;margin-left:-12em;width:9em;text-align:right;}
nav a{display:block;text-decoration:none;padding:.7em 1em;color:#000000;}
nav a:hover{background-color:#ff0000;color:#f9f9f9;-webkit-transition:color .2s linear;}
article{font-size:16px;padding-top:.5em;}
article a{color:#dd0000;text-decoration:none;}
article a:hover{color:#333333;text-decoration:underline;}
.info{font-size:14px;}

运行 app,主页显示如下:

10012616_zg52.png

接下来在 views 文件夹下新建 login.ejs ,内容如下:

<%- include header %>

<form method="post">
用户名:<input type="text" name="name" /> <br />
密码:<input type="password" name="password" /><br />
     <input type="submit" value="登录" />
</form>

<%- include footer %>

登录页面显示如下:

10012619_EFuD.png

views 文件夹下新建 reg.ejs,内容如下:

<%- include header %>

<form method="post">
    用户名:<input type="text" name="name"></input> <br />
    密码:<input type="password" name="password" ></input> <br />
    确认密码:<input type="password" name="password-repeat"></input> <br />
    邮箱:<input type="email" name="email"></input> <br />
         <input type="submit" value="注册"></input>
</form>

<%- include footer %>

注册页面显示如下:

10012621_b8SD.png

至此,未登录时的主页、注册页、登录页都已经完成。

现在,你可以启动 app 看看效果。

注意:每次我们更新代码后,都需要手动停止并且重启应用,但是这样非常麻烦。幸运的是, supervisor 模块可以解决这个问题,每当我们保存修改的文件时,supervisor 都会自动帮我们重启应用。通过以下命令,我们可以安装 supervisor

npm install -g supervisor

安装完毕之后,使用 supervisor 命令启动 app.js

supervisor app.js
页面通知

接下来我们实现用户的注册和登录操作,在这之前我们需要引入 flash 模块来实现页面通知(即成功与错误信息的显示)的功能。

我们所说的 flashconnect-flash 模块flash 是一个在 session 中用于存储信息的特定区域。信息写入 flash ,下一次显示完毕后即被清除。典型的应用是结合重定向的功能,确保信息是提供给下一个被渲染的页面。

package.json 添加一行代码:

"connect-flash": "0.1.1"

然后通过 npm install 安装 connect-flash 模块。修改 app.js 文件,在 var settings = require('./settings'); 后添加:

var flash = require('connect-flash');

app.set('view engine', 'ejs'); 后添加:

app.use(flash());

现在我们就可以使用 flash 功能了。

注册响应

前面我们已经完成了注册页,当然现在点击注册是没有效果的,因为我们还没有实现处理 POST 请求的功能,下面就来实现它。

models 文件夹下新建 user.js ,添加如下代码:

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

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

module.exports = User;

//存储用户信息
User.prototype.save = function(callback) {
  //要存入数据库的用户文档
  var user = {
      name: this.name,
      password: this.password,
      email: this.email
  };
  //打开数据库
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);//错误,返回 err 信息
    }
    //读取 users 集合
    db.collection('users', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);//错误,返回 err 信息
      }
      //将用户数据插入 users 集合
      collection.insert(user, {
        safe: true
      }, function (err, user) {
        mongodb.close();
        if (err) {
          return callback(err);//错误,返回 err 信息
        }
        callback(null, user[0]);//成功!err 为 null,并返回存储后的用户文档
      });
    });
  });
};

//读取用户信息
User.get = function(name, callback) {
  //打开数据库
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);//错误,返回 err 信息
    }
    //读取 users 集合
    db.collection('users', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);//错误,返回 err 信息
      }
      //查找用户名(name键)值为 name 一个文档
      collection.findOne({
        name: name
      }, function (err, user) {
        mongodb.close();
        if (err) {
          return callback(err);//失败!返回 err 信息
        }
        callback(null, user);//成功!返回查询的用户信息
      });
    });
  });
};

我们通过 User.prototype.save 实现了用户信息的存储,通过 User.get 实现了用户信息的读取。

然后打开 index.js ,在最前面添加如下代码:

var crypto = require('crypto'),
    User = require('../models/user.js');

通过 require() 引入 crypto 模块和 user.js 用户模型文件,cryptoNode.js 的一个核心模块,我们用它生成散列值来加密密码。

修改 index.jsapp.post('/reg') 如下:

app.post('/reg', function (req, res) {
  var name = req.body.name,
      password = req.body.password,
      password_re = req.body['password-repeat'];
  //检验用户两次输入的密码是否一致
  if (password_re != password) {
    req.flash('error', '两次输入的密码不一致!'); 
    return res.redirect('/reg');//返回注册页
  }
  //生成密码的 md5 值
  var md5 = crypto.createHash('md5'),
      password = md5.update(req.body.password).digest('hex');
  var newUser = new User({
      name: name,
      password: password,
      email: req.body.email
  });
  //检查用户名是否已经存在 
  User.get(newUser.name, function (err, user) {
    if (err) {
      req.flash('error', err);
      return res.redirect('/');
    }
    if (user) {
      req.flash('error', '用户已存在!');
      return res.redirect('/reg');//返回注册页
    }
    //如果不存在则新增用户
    newUser.save(function (err, user) {
      if (err) {
        req.flash('error', err);
        return res.redirect('/reg');//注册失败返回主册页
      }
      req.session.user = newUser;//用户信息存入 session
      req.flash('success', '注册成功!');
      res.redirect('/');//注册成功后返回主页
    });
  });
});

注意:我们把用户信息存储在了 session 里,以后就可以通过 req.session.user 读取用户信息。

  • req.body : 就是 POST 请求信息解析过后的对象,例如我们要访问 POST 来的表单内的 name = "password" 域的值,只需访问 req.body['password']req.body.password 即可。

  • res.redirect : 重定向功能,实现了页面跳转,更多关于 res.redirect 的信息请查阅这里

  • User : 在前面的代码中,我们直接使用了 User 对象。User 是一个描述数据的对象,即 MVC 架构中的模型。前面我们使用了许多视图和控制器,这是第一次接触到模型。与视图和控制器不同,模型是真实与数据打交道的工具,没有模型,网站就只是一个外壳,不能发挥真实的作用,因此它是框架中最根本的部分。

现在,我们启动应用,可以尝试注册了!注册成功后显示如下界面:

10012622_C0DP.png

这样我们并不知道是否注册成功了,我们可以查看数据库中是否存入了用户的信息,在命令行中输入 mongo 来进入 MongoDB shell 环境,如下:

10012622_1QWN.png

可以看到,用户信息已经成功存入数据库中了。

接下来,我们实现当注册返回主页时,左侧导航显示 HOME、POST、LOGOUT,右侧显示 注册成功 字样,即添加 flash 的页面通知功能。

修改 header.ejs 文件,将 <nav></nav> 修改如下:

<nav>
<span><a title="主页" href="/">home</a></span>
<% if (user) { %>
  <span><a title="发表" href="/post">post</a></span>
  <span><a title="登出" href="/logout">logout</a></span>
<% } else { %>
  <span><a title="登录" href="/login">login</a></span>
  <span><a title="注册" href="/reg">register</a></span>
<% } %>
</nav>

<article> 后面添加如下代码:

<% if (success) { %>
  <div><%= success %></div>
<% } %>
<% if (error) { %>
  <div><%= error %> </div>
<% } %>

然后修改 index.js ,将 app.get('/') 修改如下:

    app.get('/', function (req, res) {
      res.render('index', {
        title: '主页',
        user: req.session.user,
        success: req.flash('success').toString(),
        error: req.flash('error').toString()
      });
    });

app.get('reg') 修改如下:

    app.get('/reg', function (req, res) {
      res.render('reg', {
        title: '注册',
        user: req.session.user,
        success: req.flash('success').toString(),
        error: req.flash('error').toString()
      });
    });

现在运行我们的博客,注册成功后显示如下界面:

10012623_zogd.png

我们通过对 session 的使用实现了对用户状态的检测,再根据不同的用户状态显示不同的导航信息。

简单解释一下流程:用户在注册成功后,把用户信息存入 session 中,页面跳转到主页显示 注册成功! 的字样。同时把 session 中的用户信息赋给变量 user ,在渲染 index.ejs 文件时通过检测 user 判断用户是否在线,根据用户状态的不同显示不同的导航信息。

sucess: req.flash('success').toString() 的意思是将成功的信息赋值给变量 successerror: req.flash('error').toString() 的意思是将错误的信息赋值给变量 error ,然后我们在渲染 ejs 模板文件时传递这两个变量来进行检测并显示通知。

登录与登出响应

现在我们来实现用户登录的功能。

打开 index.js ,将 app.post('/login') 修改如下:

app.post('/login', function (req, res) {
      //生成密码的 md5 值
      var md5 = crypto.createHash('md5'),
          password = md5.update(req.body.password).digest('hex');
      //检查用户是否存在
      User.get(req.body.name, function (err, user) {
        if (!user) {
          req.flash('error', '用户不存在!'); 
          return res.redirect('/login');//用户不存在则跳转到登录页
        }
        //检查密码是否一致
        if (user.password != password) {
          req.flash('error', '密码错误!'); 
          return res.redirect('/login');//密码错误则跳转到登录页
        }
        //用户名密码都匹配后,将用户信息存入 session
        req.session.user = user;
        req.flash('success', '登陆成功!');
        res.redirect('/');//登陆成功后跳转到主页
      });
    });

app.get('/login') 修改如下:

app.get('/login', function (req, res) {
    res.render('login', {
        title: '登录',
        user: req.session.user,
        success: req.flash('success').toString(),
        error: req.flash('error').toString()});
});

(这样就不会出现 user is not defined 的错误了)

接下来,我们实现登出响应的函数。修改 app.get('/logout') 如下:

app.get('/logout', function (req, res) {
  req.session.user = null;
  req.flash('success', '登出成功!');
  res.redirect('/');//登出成功后跳转到主页
});

注意:通过把 req.session.user 赋值 null 丢掉 session 中用户的信息,实现用户的退出。

登录后页面显示如下:

10012623_bdHv.png

登出后页面显示如下:

10012625_zcRj.png

至此,我们实现了用户注册与登录的功能,并且根据用户登录状态显示不同的导航。

# 页面权限控制

我们虽然已经完成了用户注册与登陆的功能,但并不能阻止比如已经登陆的用户访问 localhost:3000/reg 页面,读者可亲自尝试下。为此,我们需要为页面设置访问权限。即注册和登陆页面应该阻止已登陆的用户访问,登出及后面我们将要实现的发表页只对已登录的用户开放。如何实现页面权限的控制呢?我们可以把用户登录状态的检查放到路由中间件中,在每个路径前增加路由中间件,即可实现页面权限控制。我们添加 checkNotLogincheckLogin 函数来实现这个功能。

function checkLogin(req, res, next) {
  if (!req.session.user) {
    req.flash('error', '未登录!'); 
    res.redirect('/login');
  }
  next();
}

function checkNotLogin(req, res, next) {
  if (req.session.user) {
    req.flash('error', '已登录!'); 
    res.redirect('back');//返回之前的页面
  }
  next();
}

checkNotLogincheckLogin 用来检测是否登录,并通过 next() 转移控制权,检测到未登录则跳转到登录页,检测到已登录则跳转到前一个页面。

最终 index.js 代码如下:

var crypto = require('crypto'),
    User = require('../models/user.js');

module.exports = function(app) {
  app.get('/', function (req, res) {
    res.render('index', {
      title: '主页',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    });
  });

  app.get('/reg', checkNotLogin);
  app.get('/reg', function (req, res) {
    res.render('reg', {
      title: '注册',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    });
  });

  app.post('/reg', checkNotLogin);
  app.post('/reg', function (req, res) {
    var name = req.body.name,
        password = req.body.password,
        password_re = req.body['password-repeat'];
    if (password_re != password) {
      req.flash('error', '两次输入的密码不一致!'); 
      return res.redirect('/reg');
    }
    var md5 = crypto.createHash('md5'),
        password = md5.update(req.body.password).digest('hex');
    var newUser = new User({
        name: name,
        password: password,
        email: req.body.email
    });
    User.get(newUser.name, function (err, user) {
      if (err) {
        req.flash('error', err);
        return res.redirect('/');
      }
      if (user) {
        req.flash('error', '用户已存在!');
        return res.redirect('/reg');
      }
      newUser.save(function (err, user) {
        if (err) {
          req.flash('error', err);
          return res.redirect('/reg');
        }
        req.session.user = user;
        req.flash('success', '注册成功!');
        res.redirect('/');
      });
    });
  });

  app.get('/login', checkNotLogin);
  app.get('/login', function (req, res) {
    res.render('login', {
      title: '登录',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    }); 
  });

  app.post('/login', checkNotLogin);
  app.post('/login', function (req, res) {
    var md5 = crypto.createHash('md5'),
        password = md5.update(req.body.password).digest('hex');
    User.get(req.body.name, 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('/');
    });
  });

  app.get('/post', checkLogin);
  app.get('/post', function (req, res) {
    res.render('post', {
      title: '发表',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    });
  });

  app.post('/post', checkLogin);
  app.post('/post', function (req, res) {
  });

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

  function checkLogin(req, res, next) {
    if (!req.session.user) {
      req.flash('error', '未登录!'); 
      res.redirect('/login');
    }
    next();
  }

  function checkNotLogin(req, res, next) {
    if (req.session.user) {
      req.flash('error', '已登录!'); 
      res.redirect('back');
    }
    next();
  }
};

注意:为了维护用户状态和 flash 的通知功能,我们给每个 ejs 模板文件传入了以下三个值:

user: req.session.user,
success: req.flash('success').toString(),
error: req.flash('error').toString()
# 发表文章

现在我们的博客已经具备了用户注册、登录、页面权限控制的功能,接下来我们完成博客最核心的部分——发表文章。在这一节,我们将会实现发表文章的功能,完成整个博客的设计。

页面设计

我们先来完成发表页的页面设计。在 views 文件夹下新建 post.ejs ,添加如下代码:

<%- include header %>
<form method="post">
  标题:<br />
  <input type="text" name="title" /><br />
  正文:<br />
  <textarea name="post" rows="20" cols="100"></textarea><br />
  <input type="submit" value="发表" />
</form>
<%- include footer %>
文章模型

仿照用户模型,我们将文章模型命名为 Post 对象,它拥有与 User 相似的接口,分别是 Post.getPost.prototype.savePost.get 的功能是从数据库中获取文章,可以按指定用户获取,也可以获取全部的内容。 Post.prototype.savePost 对象原型的方法,用来将文章保存到数据库。

models 文件夹下新建 post.js ,添加如下代码:

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

function Post(name, title, post) {
  this.name = name;
  this.title = title;
  this.post = post;
}

module.exports = Post;

//存储一篇文章及其相关信息
Post.prototype.save = function(callback) {
  var date = new Date();
  //存储各种时间格式,方便以后扩展
  var time = {
      date: date,
      year : date.getFullYear(),
      month : date.getFullYear() + "-" + (date.getMonth() + 1),
      day : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(),
      minute : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 
      date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) 
  }
  //要存入数据库的文档
  var post = {
      name: this.name,
      time: time,
      title: this.title,
      post: this.post
  };
  //打开数据库
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);
    }
    //读取 posts 集合
    db.collection('posts', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      //将文档插入 posts 集合
      collection.insert(post, {
        safe: true
      }, function (err) {
        mongodb.close();
        if (err) {
          return callback(err);//失败!返回 err
        }
        callback(null);//返回 err 为 null
      });
    });
  });
};

//读取文章及其相关信息
Post.get = function(name, callback) {
  //打开数据库
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);
    }
    //读取 posts 集合
    db.collection('posts', function(err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      var query = {};
      if (name) {
        query.name = name;
      }
      //根据 query 对象查询文章
      collection.find(query).sort({
        time: -1
      }).toArray(function (err, docs) {
        mongodb.close();
        if (err) {
          return callback(err);//失败!返回 err
        }
        callback(null, docs);//成功!以数组形式返回查询的结果
      });
    });
  });
};
发表响应

接下来我们给发表文章注册响应,打开 index.js ,在 User = require('../models/user.js') 后添加一行代码:

,Post = require('../models/post.js');

修改 app.post('/post') 如下:

app.post('/post', checkLogin);
    app.post('/post', function (req, res) {
      var currentUser = req.session.user,
          post = new Post(currentUser.name, req.body.title, req.body.post);
      post.save(function (err) {
        if (err) {
          req.flash('error', err); 
          return res.redirect('/');
        }
        req.flash('success', '发布成功!');
        res.redirect('/');//发表成功跳转到主页
      });
    });

最后,我们修改 index.ejs ,让主页右侧显示发表过的文章及其相关信息。

打开 index.ejs ,修改如下所示:

<%- include header %>
<% posts.forEach(function (post, index) { %>
  <p><h2><a href="#"><%= post.title %></a></h2></p>
  <p class="info">
    作者:<a href="#"><%= post.name %></a> | 
    日期:<%= post.time.minute %>
  </p>
  <p><%- post.post %></p>
<% }) %>
<%- include footer %>

打开 index.ejs ,修改 app.get('/') 如下:

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

至此,我们的博客就搭建完成了。

启动我们的博客,发表一篇博文,如下图所示:

10012626_7IIC.png

此时,查看一下数据库,如下图所示:

10012627_H1Ov.png

发现数据已经写入了数据库中。

完整代码,点击这里


Reference:

Express入门教程:一个简单的博客
Express入门教程:一个简单的博客

转载于:https://my.oschina.net/u/3579120/blog/1533488

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值