珠峰node培训使用MongoDB+Express+Aagular+Node+Bootstrap开发博客

作者:日期:2016-08-12 17:12:01 点击:295

1. express生成器生成生成项目

1.1 安装 Express生成器

express 是 Node.js 上最流行的 Web 开发框架,正如他的名字一样,使用它我们可以快速的开发一个 Web 应用。我们用 express 来搭建我们的博客,打开命令行,输入:

$ npm install -g express-generator 

安装 express 命令行工具,使用它我们可以初始化一个 express 项目。

1.2 生成一个项目

在命令行中输入:

$ express -e zhufengpeixunblog $ cd zhufengpeixunblog && npm install 

执行结果中会显示生成的文件并提示后续的命令

E:\>express -e zhufengpeixunblog  create : zhufengpeixunblog create : zhufengpeixunblog/package.json create : zhufengpeixunblog/app.js create : zhufengpeixunblog/public create : zhufengpeixunblog/routes create : zhufengpeixunblog/routes/index.js create : zhufengpeixunblog/routes/users.js create : zhufengpeixunblog/public/images create : zhufengpeixunblog/public/javascripts create : zhufengpeixunblog/views create : zhufengpeixunblog/views/index.ejs create : zhufengpeixunblog/views/error.ejs create : zhufengpeixunblog/public/stylesheets create : zhufengpeixunblog/public/stylesheets/style.css create : zhufengpeixunblog/bin create : zhufengpeixunblog/bin/www  install dependencies:  > cd zhufengpeixunblog && npm install  run the app:  > SET DEBUG=zhufengpeixunblog:* & npm start 

进入生成的目录并安装依赖

$ E:\>cd zhufengpeixunblog && npm install  debug@2.2.0 node_modules\debug └── ms@0.7.1  morgan@1.6.1 node_modules\morgan ├── on-headers@1.0.1 ├── basic-auth@1.0.3 ├── depd@1.0.1 └── on-finished@2.3.0 (ee-first@1.1.1)  cookie-parser@1.3.5 node_modules\cookie-parser ├── cookie@0.1.3 └── cookie-signature@1.0.6  serve-favicon@2.3.0 node_modules\serve-favicon ├── ms@0.7.1 ├── etag@1.7.0 ├── fresh@0.3.0 └── parseurl@1.3.0  body-parser@1.13.3 node_modules\body-parser ├── bytes@2.1.0 ├── content-type@1.0.1 ├── depd@1.0.1 ├── on-finished@2.3.0 (ee-first@1.1.1) ├── qs@4.0.0 ├── iconv-lite@0.4.11 ├── http-errors@1.3.1 (statuses@1.2.1, inherits@2.0.1) ├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7) └── raw-body@2.1.4 (unpipe@1.0.0, iconv-lite@0.4.12)  express@4.13.3 node_modules\express ├── escape-html@1.0.2 ├── array-flatten@1.1.1 ├── path-to-regexp@0.1.7 ├── merge-descriptors@1.0.0 ├── content-type@1.0.1 ├── methods@1.1.1 ├── utils-merge@1.0.0 ├── content-disposition@0.5.0 ├── range-parser@1.0.3 ├── serve-static@1.10.0 ├── vary@1.0.1 ├── depd@1.0.1 ├── cookie@0.1.3 ├── cookie-signature@1.0.6 ├── fresh@0.3.0 ├── etag@1.7.0 ├── on-finished@2.3.0 (ee-first@1.1.1) ├── parseurl@1.3.0 ├── qs@4.0.0 ├── finalhandler@0.4.0 (unpipe@1.0.0) ├── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7) ├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7) ├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1) 

设置环境变量并启动服务器,在命令行中执行下列命令

$ SET DEBUG=zhufengpeixunblog:* & npm start 

执行完成后会显示

zhufengpeixunblog:server Listening on port 3000 +0ms 

在浏览器里访问 http://localhost:3000 就可以显示欢迎页面

Express Welcome to Express 

刚才我们就用express生成器生成了一个使用ejs模板的示例工程。

1.3 debug

debug模块

var colors = require('colors'); colors.setTheme({     name: 'cyan',     cost: 'green' }); module.exports = function(name){    return function(msg){      var start = Date.now();      //判断环境变量中的DEUBG的值是是否匹配当前日志记录器的名称      var env = process.env.DEBUG;      env = env.replace('*','.*')      if(new RegExp(env).test(name)){          console.log(name.name,msg,('+'+(Date.now()-start)+'ms').cost);      }    } } 

使用debug模块

var debug = require('./debug'); //生成一个日志记录器 名字叫warn' // set DEBUG=logger:* var logger_warn = debug('logger:warn'); logger_warn('warn');  //生成一个日志记录器 名字叫error var logger_error = debug('logger:error'); logger_error('error'); 

2. 项目文件分析

2.1 生成文件说明

  • app.js:express的主配置文件
  • package.json:存储着工程的信息及模块依赖,当在 dependencies 中添加依赖的模块时,运行 npm install,npm 会检查当前目录下的 package.json,并自动安装所有指定的模块
  • node_modules:存放 package.json 中安装的模块,当你在 package.json 添加依赖的模块并安装后,存放在这个文件夹下
  • public:存放 image、css、js 等文件
  • routes:存放路由文件
  • views:存放视图文件或者说模版文件
  • bin:可执行文件,可以从此启动服务器

2.2 app.js

var express = require('express'); 加载node_modules下的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 users = require('./routes/users');  var app = express(); 生成一个express实例 app  // 设置 views 文件夹为存放视图文件的目录, 即存放模板文件的地方, app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); 设置视图模板引擎为 ejs。  //设置/public/favicon.ico为favicon图标 //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); 加载日志中间件。 app.use(bodyParser.json()); 加载解析json的中间件。 //加载解析urlencoded请求体的中间件。 app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); 加载解析cookie的中间件。 /设置public文件夹为存放静态文件的目录。 app.use(express.static(path.join(__dirname, 'public')));  app.use('/', routes); 根目录的路由 app.use('/users', users); 用户路由  // 捕获404错误,并转发到错误处理器。 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 });   }); }  //生产环境下的错误处理 // 不向用户暴露堆栈信息 app.use(function(err, req, res, next) {   res.status(err.status || 500);   res.render('error', { message: err.message, error: {}   }); });   module.exports = app;  导出app供 bin/www 使用 

2.3 bin/www

!/usr/bin/env node  表明是 node 可执行文件。  //引入我们上面app.js导出的app实例 var app = require('../app'); //引入打印调试日志的debug模块,并设置名称。 var debug = require('debug')('zhufengpeixunblog:server'); var http = require('http');  //从环境变量中获取端口号并存放到express中 var port = normalizePort(process.env.PORT || '3000'); //设置端口号 app.set('port', port);  //创建http服务器 var server = http.createServer(app);  //在所有的网络接口中监听提供的端口 server.listen(port); //监听错误事件 server.on('error', onError); //启动工程并监听3000端口,成功后打印 。 server.on('listening', onListening); //把一个端口处理成一个数字或字符串或者false function normalizePort(val) {  //先试图转成10进制数字   var port = parseInt(val, 10);    if (isNaN(port)) { // 转不成数字就当作命名管道来处理 return val;   }    if (port >= 0) { // port number 如果端口大于0就返回端口 return port;   } //不是命名管道,也不是正常端口就返回false   return false; }  //服务器的错误事件监听器 function onError(error) {  //如果系统调用不是监听则抛出错误   if (error.syscall !== 'listen') { throw error;   }   var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;    //更友好的处理特定的监听错误   switch (error.code) { 错误编码 case 'EACCES': 没有权限   //没有权限绑定指定的端口   console.error(bind + ' requires elevated privileges');   process.exit(1); 异常退出   break; case 'EADDRINUSE': 端口被占用   console.error(bind + ' is already in use');   process.exit(1);异常退出   break; default: //其它的情况抛出错误并中止进程   throw error;   } }  //服务器的监听端口成功事件回调 function onListening() { //取得服务器的地址   var addr = server.address(); //监听地址如果是字符串返回命名管道名,如果是数字返回端口   var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; //记录日志   debug('Listening on ' + bind); } 

2.4 routes/index.js

//导入express模块 var express = require('express'); //生成一个路由实例 var router = express.Router();  //当用户访问根目录也就是 / 的时候执行此回调 router.get('/', function(req, res, next) { //渲染views/index.ejs模版并显示到浏览器中   res.render('index', { title: 'Express' }); }); //导出这个路由并在app.js中通过app.use('/', routes); 加载 module.exports = router; 

2.5 views/index.ejs

在渲染模板时我们传入了一个变量 title 值为 express 字符串,模板引擎会将所有 <%= title %> 替换为 express ,然后将渲染后生成的html显示到浏览器中

<!DOCTYPE html> <html>   <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' />   </head>   <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p>   </body> </html> 

3. 功能与设计

3.1 功能分析

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

3.2 设计目标

  • 未登录:主页导航显示 首页、注册、登陆,下面显示已发表的文章、发表日期及作者。
  • 登陆后:主页导航显示 首页、发表文章、退出,下面显示已发表的文章、发表日期及作者。
  • 用户登录、注册、发表成功以及登出后都返回到主页。

3.3 未登录

3.4 登陆后

3.4 发表文章

4. 配置路由

4.1 路由规划

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

注意实现路由的时候为了避免出错,新增加的所有的路径、文件名约定都不要加s

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

4.2 增加路由

routes/user.js 中添加下列代码

/**  * 用户注册  */ router.get('/reg', function (req, res) { res.render('user/reg', {title: '注册'}); });  /**  * 当填写用户注册信息提交时的处理  */ router.post('/reg', function (req, res) { });  /**  * 显示用户登录表单  */ router.get('/login', function (req, res) { res.render('user/login', {title: '登录'}); });  /**  * 当填写用户登录信息提交时的处理  */ router.post('/login', function (req, res) { });  router.get('/logout', function (req, res) { });  routes/articles.js 中添加下列代码  router.get('/add', function (req, res) { res.render('article/add', { title: '发表文章' }); });  router.post('/add', function (req, res) {  }); 

4.3 修改app.js

app.js中添加以下代码

var article = require('./routes/article'); app.use('/article', article); 

4.4 增加模板

views下增加以下文件

4.4.1 views/article/add.ejs

<%= title %> 

4.4.2 views/user/login.ejs

<%= title %> 

4.4.3 views/user/reg.ejs

<%= title %> 

4.5 显示效果

4.5.1 用户注册

http://localhost:3000/user/reg

4.5.2 用户登陆

http://localhost:3000/user/login

4.5.3 发表文章

http://localhost:3000/article/add

5. 初始化bower

5.1 安装bower

$ npm install bower -g 

5.2 初始化bower

bower init  E:\zhufengpeixunblog>bower init ? name: zhufengpeixunblog ? version: 0.0.0 ? description: ? main file: ? what types of modules does this package expose? ? keywords: ? authors: zhangrenyang-t510 <zhang_renyang@> ? license: MIT ? homepage: ? set currently installed components as dependencies? Yes ? add commonly ignored files to ignore list? Yes  {   name: 'zhufengpeixunblog',   version: '0.0.0',   authors: [ 'zhangrenyang-t510 <zhang_renyang@>'   ],   license: 'MIT',   ignore: [ '**/.*', 'node_modules', 'bower_components', 'test', 'tests'   ] }  ? Looks good? Yes  执行完此命令后会生成一个 `bower.json` 文件。 

5.3 创建配置文件 .bowerrc

里面内容如下

{"directory":"./public/lib"} 

这表示以后bower安装的模块都安装在./public/lib下面

5.4 安装bootstrap

$ bower install bootstrap --save 

大家可以看到,不但安装了bootstrap,也安装了依赖的jquery

E:\zhufengpeixunblog>bower install bootstrap --save bower bootstrap#~3.3.5 install bootstrap#3.3.5 bower jquery#>= 1.9.1  install jquery#2.1.4 bootstrap#3.3.5 public\lib\bootstrap └── jquery#2.1.4  jquery#2.1.4 public\lib\jquery 

6. 完善页面

6.1 创建include文件夹

在views下创建include文件夹

6.2 创建 header.ejs

在include下添加包含的头部

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>珠峰博客</title> <link href="/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet"> </head> <body> <!--最外面的容器nav标签,并添加nav-bar样式类,表示这里面属于导航条 --> <nav class="navbar navbar-default" role="navigation"> <!--第二步:增加header--> <div class="navbar-header"> <!--最左侧的,默认不可见 按钮标签里嵌套了三个span的icon。 然后给与navbar-toggle样式类和属性collapse(收起), 点击的时候目标为data-target。当窗口缩小到一定程度,右侧的效果显现。--> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#menus"> <span class="sr-only">珠峰博客</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a href="/" class="navbar-brand">珠峰博客</a> </div> <!-- 放置其它的导航条 --> <div class="collapse navbar-collapse" id="menus"> <ul class="nav navbar-nav"> <li class="active"><a href="/user/reg">注册</a></li> <li><a href="/user/login">登录</a></li> <li><a href="/article/add">发表文章</a></li> <li><a href="/user/logout">登出</a></li> </ul> </div> </nav> 

在include下添加包含的尾部

<script src="/lib/jquery/dist/jquery.js"></script> <script src="/lib/bootstrap/dist/js/bootstrap.js"></script> </body> </html> 

6.4 修改首页

views/index.ejs

<% include include/header.ejs%> <div class="container">  主页 </div> <% include include/footer.ejs%> 

效果如下

6.5 修改用户注册页面

views/user/reg.ejs

   <% include ../include/header.ejs%> <div class="container"> <form action="/user/reg" method="post"  role="form" class="form-horizontal"> <div class="form-group"> <label for="username" class="col-sm-2 control-label">用户名</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-user"></span> </div> <input type="text" class="form-control" id="username" name="username" placeholder="用户名"/> </div>  </div> <div class="form-group"> <label for="email" class="col-sm-2 control-label">邮箱</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-envelope"></span></div> <input type="email" class="form-control" name="email" id="email" placeholder="邮箱"/> </div> </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label">密码</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span></div> <input type="password" class="form-control" name="password" id="password" placeholder="密码"/> </div> </div> <div class="form-group"> <label for="repassword" class="col-sm-2 control-label">确认密码</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span></div> <input type="password" class="form-control" name="repassword" id="repassword" placeholder="确认密码"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">提交</button> <button type="reset" class="btn btn-default">重置</button> </div> </div>  </form> </div> <% include ../include/footer.ejs%> 

效果如下

6.6 修改用户登录界面

views/user/login.ejs

   <% include ../include/header.ejs%> <div class="container"> <form action="/user/login" method="post"  role="form" class="form-horizontal"> <div class="form-group"> <label for="username" class="col-sm-2 control-label">用户名</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-user"></span> </div> <input type="text" class="form-control"  id="username" name="username" placeholder="用户名"/> </div>  </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label"> 密码</label> <div class="input-group col-sm-10"> <div class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span></div> <input type="password" class="form-control" name="password" id="password" placeholder="密码"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">提交</button> <button type="reset" class="btn btn-default">重置</button> </div> </div>  </form> </div> <% include ../include/footer.ejs%> 

效果如下

6.7 修改发表文章页面

views/article/add.ejs

<% include ../include/header.ejs%>    <div class="container">    <form action="/article/add" method="post" role="form" class="form-horizontal">    <div class="form-group">    <label for="title" class="col-sm-2 control-label">标题</label>    <div class="col-sm-10">    <input type="text" value="" class="form-control"  id="title" name="title" placeholder="标题"/>    </div>    </div>    <div class="form-group">    <label for="content" class="col-sm-2 control-label">正文</label>    <div class="col-sm-10">    <textarea class="form-control"   id="" cols="30" rows="10" id="content" name="content" placeholder="请输入内容"></textarea>    </div>    </div>    <div class="form-group">    <div class="col-sm-offset-2 col-sm-10">    <button type="submit" class="btn btn-default">提交</button>    <button type="reset" class="btn btn-default">重置</button>    </div>    </div>    </form>    </div>    <% include ../include/footer.ejs%> 

效果如下

7. 连接数据库

7.1 安装数据库支持

安装mongodb模块到node_modules下面并把此配置添加到package.json文件中

$ npm install mongoose --save 

7.2 创建配置文件

在工程根目录下创建 settings.js 文件,内容如下

module.exports = { cookieSecret:'zhufengkey', 用于 Cookie 加密与数据库无关 url:"mongodb://123.57.143.189:27017/zhufengblog" } 

7.3 创建db文件夹

在db文件夹下创建文件index.js,此文件存放着所有的模型 此文件负责向外暴露模型,因为Model赋给了global作为属性,那就意味着只需要引入一次就可以在程序任何地方都可以调用了

var mongoose = require('mongoose'), Schema = mongoose.Schema, models = require('./models'); var settings = require('../settings'); mongoose.connect(settings.url); mongoose.model('User', new Schema({   username:{type:String,required:true},用户名   password:{type:String,required:true},密码   email:{type:String,required:true},邮箱   avatar:{type:String,required:true}头像 })); mongoose.model('Article', new Schema( {   user:{type:ObjectId,ref:'User'}, 用户   title: String, 标题   content: String, 内容   createAt:{type: Date, default: Date.now()} 创建时间 })); global.Model = function (modelName) {  return mongoose.model(modelName); } 

7.4 在app.js中引入此模块

  require('./db'); //导入db模块 

8. 会话支持

8.1 安装会话支持模块

使用 express-sessionconnect-mongo 模块实现了将会话信息存储到mongodb中。

$ npm install express-session --save $ npm install connect-mongo --save 

8.2 修改app.js

var settings = require('./settings'); var session = require('express-session'); var MongoStore = require('connect-mongo')(session); app.use(session({   secret: settings.cookieSecret,//secret 用来防止篡改 cookie   key: settings.db,//key 的值为 cookie 的名字   //设定 cookie 的生存期,这里我们设置 cookie 的生存期为 30 天   cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},   resave:true,   saveUninitialized:true,  //设置它的 store 参数为 MongoStore 实例,把会话信息存储到数据库中,  //以避免重启服务器时会话丢失   store: new MongoStore({     db: settings.db,     host: settings.host,     port: settings.port,   }) })); 

添加完了以后我们就可以路由中通过request.session来操作会话对象了

9. 用户注册登陆

9.1 增加工具方法

routes/users.js中增加md5加密的工具方法

function md5(val){   return require('crypto').createHash('md5').update(val).digest('hex'); } 

9.2 用户注册路由

注册表单的form中action="/user/reg",所以我们要实现用户注册的路由 修改 routes/users.js

router.post('/reg', function (req, res) {   //就是 POST 请求信息解析过后的对象   var user = req.body;//   if(user.password != user.repassword){       req.flash('error','两次输入的密码不一致');       return res.redirect('/user/reg');   }   //由于repassword不需要保存,所以可以删除   delete user.repassword;   //对密码进行md5加密   user.password = md5(user.password);  //得到用户的头像   user.avatar = "https://secure.gravatar.com/avatar/" +md5(user.email)+"?s=48";   new Model('User')(user).save(function(err,user){     if(err){       req.flash('error',err);       return res.redirect('/user/reg');   }   //用户信息存入 session   req.session.user = user;   //注册成功后返回主页   res.redirect('/');   }); }); 

9.3 用户登陆路由

router.post('/login', function (req, res) {   var user = req.body;   user.password = md5(user.password);   Model('User').findOne(user,function(err,user){      if(err){        req.flash('error',err);        return res.redirect('/user/login');      }     req.session.user = user;//用户信息存入 session     res.redirect('/');//注册成功后返回主页   }); }); 

9.4 用户退出路由

router.get('/logout', function (req, res) {   req.session.user = null;//用户信息存入 session   res.redirect('/');//注册成功后返回主页 }); 

10. 发表文章路由

发表文章表单的form中action="/article/add",所以我们要实现发表文章路由 修改 routes/article.js 中的 post('/add')

router.post('/add', function (req, res) {   req.body.user = req.session.user._id;   new Model('Article')(req.body).save(function(err,article){   if(err){      return res.redirect('/article/add');    }  //发表文章成功后返回主页   res.redirect('/'); }); }); 

11. 页面消息通知

我们需要引入 flash 模块来实现页面通知(即成功与错误信息的显示)的功能。

11.1 什么是 flash?

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

11.2 安装模块

$ npm install connect-flash --save 

11.3 导入模块

在app.js中添加调用此模块

var flash = require('connect-flash'); app.use(flash()); 

11.4 发表文章成功提示

在发表文章的路由里放置flash提示信息

router.post('/add', function (req, res) {   req.body.user = req.session.user._id;   new Model('Article')(req.body).save(function(err,article){     if(err){        req.flash('error', '更新文章失败!'); //放置失败信息        return res.redirect('/article/add');     }     req.flash('success', '更新文章成功!');  //放置成功信息     res.redirect('/');//发表文章成功后返回主页   }); }); 

11.5 增加显示提示的区域

修改 views/include/header.ejs 在最底部增加以下区域

<div class="container text-center"> <% if (success) { %> <div class="alert alert-success" role="alert"><%= success %></div> <% } %> <% if (error) { %> <div class="alert alert-danger" role="alert"><%= error %></div> <% } %> </div> 

11.6 设置模板默认值

在app.js 中增加以下中间件

app.use(function(req,res,next){   res.locals.user = req.session.user;   res.locals.success = req.flash('success').toString();   res.locals.error = req.flash('error').toString();   next(); }); 

12. 控制导航

用户是否登陆看到的页面应该是不一样的,所以应该根据用户登陆状态来控制导航菜单的显示状态 修改 views/include/header.ejs

<ul class="nav navbar-nav">   <%   if(!user){%>     <li class="active"><a href="/user/reg">注册</a></li>     <li><a href="/user/login">登录</a></li>   <%}else{   %>     <li><a href="/article/add">发表文章</a></li>     <li><a href="/user/logout">登出</a></li>   <%}%> </ul> 

用户登陆后显示发表文章和登出按钮,用户登陆前显示注册和登录按钮。

13 页面权限控制

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

13.1 添加中间件

添加middleware文件夹 在middleware下添加index.js文件 middleware/index.js

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

13.2 在路由中添加中间件

修改 routes/users.js

router.get('/reg',middleware.checkNotLogin, function (req, res) {   res.render('user/reg', {title: '注册'}); }); 

修改routes/article.js

router.get('/add',middleware.checkLogin, function (req, res) {   res.render('article/add', { title: '发表文章' }); }); 

凡是不能登陆的中间加入 checkNotLogin 凡是需要登陆的中间加入 checkLogin

14 显示文章列表

14.1 修改首页模板

views/index.ejs

   <% include include/header.ejs%> <div class="container">  <ul class="media-list">   <%   articles.forEach(function(article){   %>   <li class="media">    <div class="media-left"> <a href="#">  <img class="media-object" src="<%=article.user.avatar%>" alt=""> </a>    </div>    <div class="media-body"> <h4 class="media-heading"><a href="/article/detail/<%=article._id%>"> <%=article.title%></a></h4> <p class="media-left"><%- article.content%></p>    </div>    <div class="media-bottom"> 作者:<%=article.user.username%> 发表时间:<%=article.createAt.toLocaleString()%>    </div>   </li>   <%   });   %>  </ul> </div> <% include include/footer.ejs%> 

14.2 修改路由

routes/index.js

router.get('/', function(req, res, next) {   Model('Article').find({}).populate('user').exec(function(err,articles){      res.render('index', {title: '主页',articles:articles});   }); }); 

15 支持markdown

15.1 安装markdown

$ npm install markdown -save 

15.2 修改路由

routes/index.js

markdown = require('markdown').markdown;  router.get('/', function(req, res, next) {   Model('Article').find({}).populate('user').exec(function(err,articles){      articles.forEach(function (article) {          article.content = markdown.toHTML(article.content);     });     res.render('index', {title: '主页',articles:articles});   }); }); 

16 文章详情页

16.1 修改首页

views/index.ejs

<h4 class="media-heading"> <a href="/article/detail/<%=article._id%>"><%=article.title%></a></h4> 

16.2 修改路由

routes/article.js

router.get('/detail/:_id', function (req, res) {  Model('Article').findOne({_id:req.params._id},function(err,article){    article.content = markdown.toHTML(article.content);    res.render('article/detail',{title:'查看文章',article:article});  }); }); 

16.3 增加详情页

views/article/detail.ejs

<% include ../include/header.ejs%> <div class="container">  <div class="panel panel-default">    <div class="panel-heading">       <%=article.title%>    </div>    <div class="panel-body">       <%-article.content%>    </div>    <div class="panel-footer">       <a href="/article/edit/<%=article._id%>"          class="btn btn-warning">编辑</a>        <a href="/article/delete/<%=article._id%>"          class="btn btn-danger">删除</a>     </div>  </div> </div> <% include ../include/footer.ejs%> 

17 删除文章

17.1 修改详情页

views/article/detail.ejs

 <a href="/article/delete/<%=article._id%>"    class="btn btn-danger">删除</a> 

17.2 修改路由

routes/article.js

router.get('/delete/:_id', function (req, res) {   Model('Article').remove({_id:req.params._id},function(err,result){     if(err){       req.flash('error',err);       res.redirect('back');     }     req.flash('success', '删除文章成功!');     res.redirect('/');//注册成功后返回主页   }); }); 

18 编辑文章

18.1 修改详情页

views/article/detail.ejs

<a href="/article/edit/<%=article._id%>" class="btn btn-warning">编辑</a> 

18.2 修改添加文章界面

views/article/add.ejs

<form action="/article/add" method="post"  role="form" class="form-horizontal" enctype="multipart/form-data">  <input type="hidden" value="<%=article._id%>" name="_id"/>  <div class="form-group">  <label for="title" class="col-sm-2 control-label">标题</label>  <div class="col-sm-10">  <input type="text" value="<%=article.title%>" class="form-control" id="title" name="title" placeholder="标题"/>  </div>  </div>  <div class="form-group">  <label for="content" class="col-sm-2 control-label">正文</label>  <div class="col-sm-10">  <textarea class="form-control"   id="" cols="30" rows="10" id="content" name="content" placeholder="请输入内容" ><%=article.content%></textarea>  </div>  </div>  <div class="form-group">  <label for="img" class="col-sm-2 control-label">图片</label>  <div class="col-sm-10">  <%  if(article.img){  %>   <img src="<%=article.img%>" style="width:100px;height:100px" alt=""/>  <%  }   %>  <input type="file" class="form-control"  name="img" id="img"/>  </div>  </div>  <div class="form-group">  <div class="col-sm-offset-2 col-sm-10">  <button type="submit" class="btn btn-default">提交</button>  <button type="reset" class="btn btn-default">重置</button>  </div>  </div> </form> 

18.3 修改路由

routes/article.js

router.post('/add',middleware.checkLogin,upload.single('img'),  function (req, res) {   var _id = req.body._id;   if(_id){     var set = {title:req.body.title,content:req.body.content};     Model('Article').update({_id:_id},{$set:set},function(err,result){       if(err){         req.flash('error',err);         return res.redirect('back');     }     req.flash('success', '更新文章成功!');     res.redirect('/');//注册成功后返回主页     });   }else{     req.body.user = req.session.user._id;     new Model('Article')(req.body).save(function(err,article){     if(err){       req.flash('error',err);       return res.redirect('/article/add');     }     req.flash('success', '发表文章成功!');     res.redirect('/');//注册成功后返回主页   });   } });  router.get('/edit/:_id', function (req, res) {   Model('Article').findOne({_id:req.params._id},function(err,article){     res.render('article/add',{title:'编辑文章',article:article});   }); }); 

19. 搜索和分页

19.1 在导航栏增加搜索框

views/include/header.ejs

<form  class="navbar-form navbar-right" role="search" method="get" action="/article/list/1/2">   <div class="form-group">     <label for="keyword">关键字</label>     <input type="text" name="keyword" id="keyword"     class="form-control" value="<%=keyword%>"/>   </div>   <button type="submit" class="btn  btn-default" value="search"    name="searchBtn">搜索</button> </form> 

19.2 在首页增加分页条

views/index.ejs

 <ul class="pagination">   <%   for(var i=1;i<=totalPage;i++){   %>   <li><a href="/article/list/<%=i%>/<%=pageSize%>?keyword=<%=keyword%>">        <%=i%></a></li>   <%   }   %>  </ul> 

19.3 首页导航重定向到文章列表页

routes/index.js  router.get('/', function(req, res, next) {   res.redirect('/article/list/1/2'); }); 

19.4 增加文章列表导航

routes/article.js

router.get('/list/:pageNum/:pageSize',function(req, res, next) {   var pageNum = req.params.pageNum&&req.params.pageNum>0? parseInt(req.params.pageNum):1;   var pageSize =req.params.pageSize&&req.params.pageSize>0? parseInt(req.params.pageSize):2;   var query = {};   var searchBtn = req.query.searchBtn;   var keyword = req.query.keyword;   if(searchBtn){     req.session.keyword = keyword;   }   if(req.session.keyword){     query['title'] = new RegExp(req.session.keyword,"i");   }    Model('Article').count(query,function(err,count){     Model('Article').find(query).sort({createAt:-1})       .skip((pageNum-1)*pageSize)      .limit(pageSize).populate('user').exec(function(err,articles){          articles.forEach(function (article) {             article.content = markdown.toHTML(article.content);          });          res.render('index',{              title:'主页',              pageNum:pageNum,              pageSize:pageSize,              keyword:req.session.keyword,              totalPage:Math.ceil(count/pageSize),              articles:articles          });        });   }); }); 

20. 实现评论功能

20.1 修改模板

views/article/detail.ejs

<div class="panel panel-default">   <div class="panel-heading">   评论列表   </div>   <div class="panel-body"  style="height:300px;overflow-y: scroll">   <ul class="media-list">       <%       article.comments.forEach(function(comment){       %>          <li class="media">             <div class="media-left">               <a href="#">                   <img class="media-object"                     src="<%=comment.user.avatar%>" alt="">               </a>             </div>             <div class="media-body">                <p class="media-left"><%- comment.content%></p>             </div>             <div class="media-bottom">                 <%=comment.user.username%>                  <%=comment.createAt.toLocaleString()%>             </div>          </li>       <%       });       %>   </ul>   </div>  </div>  <div class="panel panel-default">   <form action="/article/comment" method="post">     <input type="hidden" value="<%=article._id%>" name="_id"/>     <div class="panel-body">       <textarea class="form-control"   id="" cols="30" rows="10"        id="content" name="content" placeholder="请输入评论" ></textarea>     </div>     <div class="panel-footer">        <button type="submit" class="btn btn-default">提交</button>     </div>     </form> </div> 

20.2 修改模型

db/models.js

 comments: [{user:{type:ObjectId,ref:'User'},content:String,    createAt:{type: Date, default: Date.now}}], 

20.3 修改路由

routes/article.js

router.post('/comment',middleware.checkLogin, function (req, res) {    var user = req.session.user;    Model('Article').update({_id:req.body._id},      {$push:{comments:{user:user._id,content:req.body.content}}},      function(err,result){          if(err){              req.flash('error',err);              return res.redirect('back');           }          req.flash('success', '评论成功!');         res.redirect('back');    }); }); 

21 显示PV和评论

21.1 安装async

$  npm install async --save 

21.2 修改模型

db/models.js

pv: {type:Number,default:0}, 

21.3 修改路由

routes/article.js

var async = require('async'); router.get('/detail/:_id', function (req, res) {     async.parallel([function (callback) {         Model('Article').findOne({_id: req.params._id})             .populate('user').populate('comments.user')             .exec(function (err, article) {                 article.content = markdown.toHTML(article.content);                 callback(err, article);             });     }, function (callback) {         Model('Article').update({_id: req.params._id}         ,{$inc: {pv: 1}}, callback);     }], function (err, result) {         if (err) {             req.flash('error', err);             res.redirect('back');         }         res.render('article/detail',        {title: '查看文章', article: result[0]});     }); }); 

21.4 修改模板

views/index.ejs

阅读:<%= article.pv %>| 评论:<%= article.comments.length%> 

22. 美化404中间件

22.1 修改404中间件

app.js

// catch 404 and forward to error handler app.use(function(req, res, next) {   res.render("404"); }); 

22.2 增加404页面模板

views/404.ejs

<% include include/header.ejs%> <div class="container">   <img src="" alt="404"/> </div> <% include include/footer.ejs%> 

23 打印日志

现在给博客增加日志,实现访问日志(access.log)和错误日志(error.log)功能 把日志保存为日志文件 app.js

//正常日志 var accessLog = fs.createWriteStream('access.log', {flags: 'a'}); app.use(logger('dev',{stream: accessLog}));  //错误日志 var errorLog = fs.createWriteStream('error.log', {flags: 'a'}); app.use(function (err, req, res, next) {   var meta = '[' + new Date() + '] ' + req.url + '\n';   errorLog.write(meta + err.stack + '\n');   next(); }); 

24 发布heroKu

https://www.heroku.com/

24.1 注册

24.2 验证邮箱

可用qq,不能126 163等

24.3 查看邮箱点击激活链接

24.4 需要设置密码

24.5 验证成功

24.6 进入控制面板

点击右上角的创建app按钮

24.7 输入app名称

24.8 将此APP关联到github上

要授权github登陆

24.9 点击布署分支

24.10 发布结果

演示地址 项目源码

珠峰培训node培训之MongoDB+Express+Angular+Node+Bootstrap+Linux开发的爬虫

珠峰node培训使用MongoDB+Express+Angular+Node+Bootstrap开发聊天室

珠峰node培训使用MongoDB+Express+Aagular+Node+Bootstrap开发博客

上一篇: 珠峰node培训之node框架express使用

下一篇: 珠峰node培训使用MongoDB+Express+Angular+Node+Bootstrap开发聊天室