《nodejs开发指南》express4.x版-微博案例完整实现

本文参考了
《nodejs开发指南》微博实例express4.x版
https://segmentfault.com/a/1190000002620309

本来网上也有了很多相关的教程,写本文的目的主要是希望梳理对整个代码的认识,另一方面,参考的那篇文章某些地方的实现目前也不适用,需要更新。也欢迎大家与我交流^^。
本文尝试完整实现整个例子,因此将不尝试区分与《nodejs开发指南》实现的差异。

完整代码下载

https://github.com/haishangfeie/weibo

开发详细步骤

创建项目:

express -e microblog

《nodejs开发指南》express4.x版-微博案例完整实现_第1张图片
按提示输入

PS E:\code\nodejsExercise\express\3> cd .\microblog\
PS E:\code\nodejsExercise\express\3\microblog> npm i

现在,我们先启动网站看看

npm start

《nodejs开发指南》express4.x版-微博案例完整实现_第2张图片
如果能运行到以上的效果,那么项目已经创建好了。

功能分析

那么在正式开始创建网站前,我也试着对接下来的项目进行一个功能分析。
本项目是一个微博项目的简单实现,需要包括如下功能:用户的登录、注册、退出登录,另外还有信息登录功能。
大致规划:

  • 一个主页用于显示微博的主体内容
  • 一个登录页面
  • 一个注册页面
  • 一个用户页面,只显示用户的微博信息

接下来开始正式的搭建项目。

可以先写一个index.html页面看看效果:
《nodejs开发指南》express4.x版-微博案例完整实现_第3张图片

接着将它改为模板:
在views文件夹新建一个header.ejs


<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><%= title %> - Microblogtitle>
  <link rel='stylesheet' href='/stylesheets/bootstrap.css' />
  <style type="text/css">
  body {
    padding-top: 60px;
    padding-bottom: 40px;
  }
style>
<link href="stylesheets/bootstrap-responsive.css" rel="stylesheet">
head>
<body>
    <div class="navbar navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container">
          <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
            <span class="icon-bar">span>
            <span class="icon-bar">span>
            <span class="icon-bar">span>
          a>
          <a class="brand" href="/">Microbloga>
          <div class="nav-collapse">
            <ul class="nav">
              <li class="active"><a href="/">首頁a>li>
              <li><a href="/login">登入a>li>
              <li><a href="/reg">註冊a>li>
            ul>
          div>
        div>
      div>
    div>

在views文件夹新建一个footer.ejs

    <hr />
    <footer>
      <p><a href="http://www.byvoid.com/" target="_blank">BYVoida> 2012p>
    footer>
  div>
body>
<script src="/javascripts/jquery.js">script>
<script src="/javascripts/bootstrap.js">script>
html>

在views文件夹,修改index.ejs模板如下:

<% include header.ejs %>
  <div class="hero-unit">
    <h1>歡迎來到 Microblogh1>
    <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>
<% include footer.ejs %>

在public文件夹中放入图片、js/css文件
可以从我的源码直接拷贝。
修改routes文件夹index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {

  res.render('index', { title: '首页' }); //修改了这里
});

module.exports = router;

此时页面如下:
《nodejs开发指南》express4.x版-微博案例完整实现_第4张图片
好,index已经做好了,然后开始着手制作登录界面。
登录操作涉及数据库,因此首先我们先安装数据库:
相关设置参考网址:
http://blog.csdn.net/sixp512720288/article/details/52472887
下载地址:
http://dl.mongodb.org/dl/win32/x86_64
安装包名字:
mongodb-win32-x86_64-2008plus-ssl-3.6.0-rc6.zip

我的电脑是win10 64位的,可能与你们的情况不一样,上面的数据库的安装与运行仅供参考。
用到数据库前都需要启动:
运行cmd,如果已经按上文的网址配置,去到数据库的bin文件夹运行,-dbpath后的路径请按照你的具体配置修改。
请记住以下代码,启动网站前记得都要先启动了数据库,建议现在就先启动避免一会忘了启动报错

.\mongod.exe -dbpath "E:\mongodb\data\db"

使用数据库前,还需要安装一些依赖,进行一些设置:
首先,给package.json一行代码(不知道写在哪的,具体可参考源码):

"mongodb": ">=0.9.9"

cmd

npm i

创建一个settings.js文件

module.exports = {
  cookieSecret:'microblogbyvoid',  //用于cookie的加密
  db:'microblog', //数据库的名字
  host:'localhost', //数据库地址
}

创建models文件夹,在此文件夹中创建db.js

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

接下来需要将用户数据存储到数据库中,你觉得需要做些什么呢?
为了将用户数据存储到数据库中,做出如下配置:

  • 新增一个connect-mongo模块:
"express-session": "^1.15.6",
"connect-mongo": ">= 0.1.7"

cmd

npm i

对app.js进行修改,新增:

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

以及

app.use(session({
  secret:settings.cookieSecret,
  store:new MongoStore({
    // db:settings.db
    url: 'mongodb://localhost/microblog'
  })
}));

接下来是注册页面以及登录界面
在views页面新建reg.ejs模版

<% include header.ejs %>
class="form-horizontal" method="post" >
用戶註冊 <div class="control-group"> <div class="controls"> "text" class="input-xlarge" id="username" name="username">

class="help-block">你的賬戶的名稱,用於登錄和顯示。

div> div> <div class="control-group"> <div class="controls"> "password" class="input-xlarge" id="password" name="password"> div> div> <div class="control-group"> <div class="controls"> "password" class="input-xlarge" id="password-repeat" name="password-repeat"> div> div> <div class="form-actions"> div>
<% include footer.ejs %>

在views页面新建login.ejs模版

<% include header.ejs %>
class="form-horizontal" method="post">
用戶登入 <div class="control-group"> <div class="controls"> "text" class="input-xlarge" id="username" name="username"> div> div> <div class="control-group"> <div class="controls"> "password" class="input-xlarge" id="password" name="password"> div> div> <div class="form-actions"> div>
<% include footer.ejs %>

修改index.js,引入模版渲染页面

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

router.get('/login', function(req, res, next) {
  res.render('login', { title: '用户登入' });
});

启动网页效果如下:
《nodejs开发指南》express4.x版-微博案例完整实现_第5张图片

《nodejs开发指南》express4.x版-微博案例完整实现_第6张图片

至此注册、登陆页面均能正常显示了,是时候给页面增加一些响应功能了。
先做注册界面的功能:
需要做些什么呢?
- 验证用户名是否存在-这个需要读取数据库的内容,进行比对
- 验证密码是否一致
- 进行必要的密码保护
- 验证无误后将用户名和密码保存到数据库
- 不管验证结果是正确还是错误,提交页面后要给出一个反馈
这里主要对涉数据库的操作进行一下分析:
验证用户名需要读取数据库的用户信息,而保存用户名和密码到数据库是要新增数据库的用户信息。这些功能可以抽离出来,
用User这个构造函数实现这些功能。
User.get()用于获取用户信息,
user.save()用于将特定实例保存到数据库。
由于代码用到crypto、user,先添加模块(其实我是写完了post才添加的,不过为了避免后面添加时大家都忘了之前的代码了,先添加了),在index.js添加:

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

然后,代码先写成这样:

router.post('/reg',function(req,res,next){
  var md5 = crypto.createHash('md5');
  var password = md5.update(req.body.password);
  var newUser =new User({
    name:req.body.username,
    password:password
  });
  User.get(newUser.name,function(err,user){
    if(err){
      //反馈错误,跳转到/reg,记得return

    }
    //判断用户是否存在
    if(user){
      //反馈用户存在,跳转到/reg,记得return
    }
    //判断密码是否一致
    if(req.body.password !== req.body['password-repeat'] ){
      //反馈密码不一致,跳转到/reg,记得return
    }
    //用户不存在
    newUser.save(function(err){
      if(err){
        //反馈错误,跳转到/reg,记得return
      }
      //反馈注册成功,跳转到/。      
    });
  });
});

代码写到这,还有一些问题没有解决:

  • User构造函数未定义
  • 反馈未实现

先解决构造函数的问题:
models文件夹新建user.js

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

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

User.prototype.save = function(callback){
  //存入mongodb文档
  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(username,callback){
  mongodb.open(function(err,db){
    if(err){
      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);
        }
      });
    });
  });
};
module.exports = User;

写到这里,先尝试简单验证一下User函数是否存在问题。
将代码修改为:(是修改不是新增)

router.post('/reg',function(req,res,next){
  var md5 = crypto.createHash('md5');
  var password = md5.update(req.body.password);
  var newUser =new User({
    name:req.body.username,
    password:password
  });
  User.get(newUser.name,function(err,user){
    if(err){
      //反馈错误,跳转到/reg,记得return
      console.log(err);
      return res.redirect('/reg');
    }
    //判断用户是否存在
    if(user){
      console.log('user existed');
      return res.redirect('/reg');
    }
    //判断密码是否一致
    if(req.body.password !== req.body['password-repeat'] ){
      //反馈密码不一致,跳转到/reg,记得return
      console.log('password not equal');
      return res.redirect('/reg');
    }
    //用户不存在
    newUser.save(function(err){
      if(err){
        //反馈错误,跳转到/reg,记得return
        console.log('save failure');
        return res.redirect('/reg');
      }
      console.log('save success');
      res.redirect('/');
    });
  });
});

这个可以自行测试,就不截图了。
至此,注册还有一个反馈的功能未实现。
为此,引入新的模块,
package.json:

    "connect-flash": "^0.1.1"

cmd

npm i

app.js

var flash = require('connect-flash');
app.use(flash());
app.use(function(req,res,next){
  console.log('app.user 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();
});

再次修改index.js

router.post('/reg',function(req,res,next){
  var md5 = crypto.createHash('md5');
  var password = md5.update(req.body.password);
  var newUser =new User({
    name:req.body.username,
    password:password
  });
  User.get(newUser.name,function(err,user){
    if(err){
      //反馈错误,跳转到/reg,记得return
      req.flash('error',err);
      return res.redirect('/reg');
    }
    //判断用户是否存在
    if(user){
      req.flash('error','用户已存在');
      return res.redirect('/reg');
    }
    //判断密码是否一致
    if(req.body.password !== req.body['password-repeat'] ){
      //反馈密码不一致,跳转到/reg,记得return
      req.flash('error','密码不一致');
      return res.redirect('/reg');
    }
    //用户不存在
    newUser.save(function(err){
      if(err){
        //反馈错误,跳转到/reg,记得return
        req.flash('error','保存失败');
        return res.redirect('/reg');
      }
      req.flash('success','保存成功');
      res.redirect('/');
    });
  });
});

同时,在header.ejs结尾处添加以显示反馈:

    <div id="container" class="container">
      <% if (success) { %>
      <div class="alert alert-success">
        <%= success %>
      div>
      <% } %>
      <% if (error) { %>
      <div class="alert alert-error">
        <%= error %>
      div>
      <% } %>

现在可以先测试一下,应该已经可以注册,并且每次注册均会有反馈。
《nodejs开发指南》express4.x版-微博案例完整实现_第7张图片

然后就是登入/登出的页面
上面,登入界面已经做好了,登出直接点击就登出了,不需要额外制作界面。但是现在页面没有登出的界面,需要加上去。登出的按钮只在登陆后才出现。
为此,可以修改header.ejs

            <ul class="nav">
              <li class="active"><a href="/">首頁a>li>
              <% if (!user) { %>
              <li><a href="/login">登入a>li>
              <li><a href="/reg">註冊a>li>
              <% } else { %>
              <li><a href="/logout">登出a>li>
              <% } %>
            ul>

做登入的响应,在index.js添加如下代码:

router.post('/login',function(req,res,next){
  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','登入成功');
    return res.redirect('/');
  });
});

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

至此,登入登出功能已经完成。
接下来,对页面权限进行控制:
在index.js中添加

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

并将代码修改为如下,可参考源码:

《nodejs开发指南》express4.x版-微博案例完整实现_第8张图片

接着就是微博的界面了
为了方便,先做了微博模型,与User类似的Post。它的功能也是获取与保存,只不过数据从用户信息变成了发表的微博信息。
首先,我们来思考一下,Post具体要做什么呢?
Post创建的对象应该包含微博正文、用户名、时间这三个信息;
用户发信息的时候,实例post.save()需要保存微博正文、用户名、时间这三个信息,这些信息都包含在实例中了,因此可以不传进去,只需设置一个callback(err)即可。
Post.get是获取微博,这里的设想是有两种模式,一种是指定用户获取,一种是获取全部,因此其可以传入用户名或者null(显示全部),另外需要一个callback。

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){
  //存入Mongodb 的文档
  var post = {
    user:this.user,
    post:this.post,
    time:this.time
  };
  mongodb.open(function(err,db){
    if(err){
      return callback(err);
    }
    //读取posts集合
    db.collection('posts',function(err,collection){
      if(err){
        mongodb.close();
        return callback(err);
      }
      //为user属性添加索引
      collection.ensureIndex('user');
      //写入post文档
      collection.insert(post,{safe:true},function(err,post){
        mongodb.close();
        callback(err);
      });
    });
  });
};

Post.get =function get(username,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);
      }
      //查找user属性为username的文档,如果username是null则匹配全部
      var query = {};
      if(username) {
        query.user = username;
      }
      collection.find(query).sort({time:-1}).toArray(function(err,docs){
        mongodb.close();
        if(err){
          callback(err,null);
        }
        //封装posts为Post对象
        var posts = [];
        docs.forEach(function(doc,index){
          var post = new Post(doc.user,doc.post,doc.time);
          posts.push(post);
        });
        callback(null,posts);
      });
    });
  });
};

修改index.ejs,用于显示微博文章。

<% include header.ejs %>
<% if (!user) { %>
  <div class="hero-unit">
    <h1>歡迎來到 Microblogh1>
    <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.ejs %>
<% } %>
<% include posts.ejs %>
<% include footer.ejs %>

这里用到了say.ejs以及posts.ejs,可以参考源码,因为这个模板后面也不需要修改了,就不列出来了。
修改index.js,传入微博信息给模板:

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

/* GET home page. */
router.get('/', function(req, res, next) {
  Post.get(null,function(err,posts){
    if(err){
      posts=[];
    }
    res.render('index', { 
      title: '首页',
      posts: posts,
     });
  });
});
//用于发表微博
router.post('/post',checkLogin);
router.post('/post',function(req,res,next){
  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);
  });
});

还需要加入一个用户界面:
user.ejs
接着在index.js添加其响应

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

至此,整个微博的案例基本完成了。

参考文献:
http://www.cnblogs.com/yuanzm/p/3770986.html
http://blog.csdn.net/sixp512720288/article/details/52472887
http://cnodejs.org/topic/50367e6ff767cc9a51d2e021

你可能感兴趣的:(前端开发笔记,nodejs开发)