暂存

Twitch 商城网站

介绍:

1、Node.js搭建的网站,采用Mongodb数据库
2、后台框架 借用了 express ,模板引擎使用的 ejs,对于数据库的操作使用的是 mongoose

网站搭建流程


入口文件 app.js

我们来作一些修改,以上代码实现了路由的功能,我们当然可以不要 routes/index.js 文件,把实现路由功能的代码都放在 app.js 里,但随着时间的推移 app.js 会变得臃肿难以维护,这也违背了代码模块化的思想,所以我们把实现路由功能的代码都放在 routes/index.js 里。官方给出的写法是在 app.js 中实现了简单的路由分配,然后再去 index.js 中找到对应的路由函数,最终实现路由功能。我们不妨把路由控制器和实现路由功能的函数都放到 index.js 里,app.js 中只有一个总的路由接口。

    var routes = require('./routes/index');
    //在执行到错误处理程序前 将 express实例 app 传给 路由
    routes(app);

routes/index.js 文件,总的路由接口

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

app.js 和 index.js 修改成这样就达成了总路由接口的配置了!

路由规则

express 封装了多种 http 请求方式,我们主要只使用 get 和 post 两种,即 app.get() 和 app.post()
app.get() 和 app.post() 的第一个参数都为请求的路径,第二个参数为处理请求的回调函数,回调函数有两个参数分别是 req 和 res,代表请求信息和响应信息 。路径请求及对应的获取路径有以下几种形式:这里就参考文档

解析 :

  • req.query: 处理 get 请求,获取 get 请求参数
  • req.params: 处理 /:xxx 形式的 get 或 post 请求,获取请求参数
  • req.body: 处理 post 请求,获取 post 请求体
  • req.param(): 处理 get 和 post 请求,但查找优先级由高到低为 req.params→req.body→req.query

路径规则还支持正则表达式,更多请查阅 Express 官方文档

接下来自己添加一个路由规则做下测试吧!

模板引擎


什么是模板引擎

模板引擎(Template Engine)是一个将页面模板和要显示的数据结合起来生成 HTML 页面的工具。
如果说上面讲到的 express 中的路由控制方法相当于 MVC 中的控制器的话,那模板引擎就相当于 MVC 中的视图。

什么事ejs?

ejs 是模板引擎的一种,也是我们这个教程中使用的模板引擎,因为它使用起来十分简单,而且与 express 集成良好。

使用模板引擎

前面我们通过以下两行代码设置了模板文件的存储位置和使用的模板引擎:

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');

注意:我们通过 express -e blog 只是初始化了一个使用 ejs 模板引擎的工程而已,比如 node_modules 下添加了 ejs 模块,views 文件夹下有 index.ejs 。并不是说强制该工程只能使用 ejs 不能使用其他的模板引擎比如 jade,真正指定使用哪个模板引擎的是 app.set('view engine', 'ejs'); 。

在 routes/index.js 中通过调用 res.render() 渲染模版,并将其产生的页面直接返回给客户端。它接受两个参数,第一个是模板的名称,即 views 目录下的模板文件名,扩展名 .ejs 可选。第二个参数是传递给模板的数据对象,用于模板翻译。

当我们 res.render('index', { title: 'Express' }); 时,模板引擎会把 <%= title %> 替换成 Express,然后把替换后的页面显示给用户。

注意:我们通过 app.use(express.static(path.join(__dirname, 'public'))) 设置了静态文件目录为 public 文件夹,所以上面代码中的 href='/stylesheets/style.css' 就相当于 href='public/stylesheets/style.css'

ejs 的标签系统非常简单,它只有以下三种标签:

  • <% code %>:JavaScript 代码。
  • <%= code %>:显示替换过 HTML 特殊字符的内容。
  • <%- code %>:显示原始 HTML 内容。

注意: <%= code %> 和 <%- code %> 的区别,当变量 code 为普通字符串时,两者没有区别。当 code 比如为 <h1>hello</h1> 这种字符串时,<%= code %> 会原样输出 <h1>hello</h1>,而 <%- code %> 则会显示 H1 大的 hello 字符串。

我们可以在 <% %> 内使用 JavaScript 代码。下面是 ejs 的官方示例:

The Data

supplies: ['mop', 'broom', 'duster']

The Template

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
   <li><%= supplies[i] %></li>
<% } %>
</ul>   

The Result

<ul>
  <li>mop</li>
  <li>broom</li>
  <li>duster</li>
</ul>

我们可以用上述三种标签实现页面模板系统能实现的任何内容。

页面布局

这里我们不使用layout进行页面布局,而是使用更为简单灵活的include。include 的简单使用如下:

index.ejs

<%- include a %>
hello,world!
<%- include b %>

a.ejs

this is a.ejs

b.ejs

this is b.ejs

最终index.ejs会显示

this is a.ejs
hello,world!
this is b.ejs

根据上面,我们具体初步的了解了一些nodejs、express一些特性,接下来正式开始搭建多人博客。


搭建多人博客


功能分析

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

设计目标

未登录:主页左侧导航显示 home、login、register,右侧显示已发表的文章、发表日期及作者。
登陆后:主页左侧导航显示 home、post、logout,右侧显示已发表的文章、发表日期及作者。
用户登录、注册、发表成功以及登出后都返回到主页。

未登录效果图:
主页:

未登录主页

登录页:


登录页
登录页

注册页:


注册页
注册页

登录后的效果图:
主页:

主页
主页

发表页:


发表页
发表页

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

路由规划

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

根据构思的设计图,我们作以下路由规划:

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

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

修改 index.js 如下:

// 路由控制器和实现路由功能的函数,都在这个文件里

module.exports = function(app){
    // 主页
    app.get('/', function(req, res) {
      res.render('index', { title: 'Express' });
    });
    
    // 注册,跳转注册页
    app.get('/reg',function(req,res){
      res.render('reg',{title:'注册'});   
    });
    // 注册,注册操作
    app.post('/reg',function(req,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){
        
    });

};

使用数据库


MongoDB简介

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

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

更多有关 MongoDB 的知识请参阅 《mongodb权威指南》或查阅:http://www.mongodb.org/

安装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 模块就下载并安装完成了。

创建setting.js文件

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

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

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

创建Models文件夹

接下来在根目录下新建 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');

会话支持 session

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

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

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

在package.json修改 "mongodb":“2.0.42”, “connect-mongo”:“0.8.2” 运行npm install安装模块,打开app.js,添加以下代码:

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

app.use(session({
  secret: settings.cookieSecret,
  key: settings.db,//cookie name
  cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days
  store: new MongoStore({
    db: settings.db,
    host: settings.host,
    port: settings.port
  })
}));

注意: connect-mongo 最新版需要改成如:

store: new MongoStore({
  url: 'mongodb://localhost/blog'
})

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

实现注册和登陆


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

页面设计

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

主页:index.ejs

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

登录页:login.ejs

<%- include header %>
    <form method="post">
      用户名: <input type="text" name="name"/><br /><br />
      密   码:  <input type="password" name="password"/><br /><br />
             <input type="submit" value="登录"/>
             <input type="reset" value="重置"/>
    </form>
<%- include footer %>

注册页: reg.ejs

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

到这里 启动我们的博客看看吧!

实时更新组件

注意:每次我们更新代码后,都需要手动停止并重启应用,使用 supervisor 模块可以解决这个问题,每当我们保存修改的文件时,supervisor 都会自动帮我们重启应用。通过:

$ npm install -g supervisor

安装 supervisor 。使用 supervisor 命令启动 app.js:

$ supervisor app.js

页面通知

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

什么事Flash?

我们所说的 flash 即 connect-flash 模块(https://github.com/jaredhanson/connect-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;

// 存储用户信息 prototype 原型
User.prototype.save = function(callback) {
    // Scheme 要存入数据库的用户文档
    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);
            }
                    // 将用户数据插入 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);
        }
        // 读取users 集合
        db.collection('users',function(err,collection)
        {
            if(err) {
                mongodb.close();
                return callback(err);
            }
            // 查找用户名(name 键)值为 name 的一个文档
            collection.findOne({name:name},function(err,user){
                if(err) {
                    return callback(err);//失败!返回错误信息
                }
                callback(null,user);//成功!返回查询的用户信息
            });
        });
    });
};

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

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

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

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

修改 index.js 中 app.post('/reg') 如下:

注意:我们把用户信息存储在了 session 里,以后就可以通过 req.session.user 读取用户信息。
  • req.body: 就是 POST 请求信息解析过后的对象,例如我们要访问 POST 来的表单内的 name="password" 域的值,只需访问 req.body['password'] 或 req.body.password 即可。
  • res.redirect: 重定向功能,实现了页面的跳转,更多关于 res.redirect 的信息请查阅:http://expressjs.com/api.html#res.redirect
  • User:在前面的代码中,我们直接使用了 User 对象。User 是一个描述数据的对象,即 MVC 架构中的模型。前面我们使用了许多视图和控制器,这是第一次接触到模型。与视图和控制器不同,模型是真正与数据打交道的工具,没有模型,网站就只是一个外壳,不能发挥真实的作用,因此它是框架中最根本的部分。

接下来我们实现当注册成功返回主页时,左侧导航显示 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()
  });
});

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

注册成功
注册成功

这个时候访问别的就会 出现一个 user is undefined。因为其他的路由还没传递一个user 过去。

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

success: req.flash('success').toString() 的意思是将成功的信息赋值给变量 success, error: 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 中用户的信息,实现用户的退出。

登录后页面显示如下:

登录成功
登录成功
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容