博客十

作者: 我为峰2014 | 来源:发表于2017-12-17 17:37 被阅读0次

    前言

    我们通过引入会话(session)机制来记录用户登录状态,来判断用户是否已经登陆,针对已登录和未登录的用户显示不同的内容。
    但是我们还有文章数据保存在哪里呢,我们的数据量假如很小可以考虑使用json文件来保存,但是我们打算做一个blog系统,会使用到增查删改,还是直接上数据库吧,Express配合MongoDB更加湿滑哦。

    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权威指南》
    或者http://www.runoob.com/mongodb/mongodb-tutorial.html

    连接MongoDB

    数据库自行安装启动,简单介绍一下,如何连接,在 Node.js 中使用 MongoDB ,我们一般需要一个驱动模块来连接数据库,

    官方的 mongodb(也叫 node-mongodb-native)库,它简单。参照文档即可上手,但是不支持不支持文档校验,而且写出的代码很沉余。

    大多数人会选择第三方比较优秀的Mongoose,Mongoose 通过 Schema 支持文档校验,虽说 mongodb 是 no schema 的,但在生产环境中使用 Schema 有两点好处。一是对文档做校验,防止非正常情况下写入错误的数据到数据库,二是可以简化一些代码,如类型为 ObjectId 的字段查询或更新时可通过对应的字符串操作,不用每次包装成 ObjectId 对象。

    在express的骨架中在新建一个models文件夹,这个文件夹用来存放我们的模型,我们使用Mongoose这个模块操作 mongodb 进行增删改查。在 myblog 下新建 lib 目录,在该目录下新建 mongo.js,添加如下代码:

    lib/mongo.js

    const config = require('config-lite')(__dirname)
    var mongoose = require('mongoose'); 
    mongoose.connect(config.mongodb)
    

    思考

    其实我们现在的设计就有一点MVC的影子,每一个人对MVC的思考都不一样,我才疏学浅,是这样认为的,models代表M,存放可以对数据库增删查改的模型,routes代表 C,存放我们控制返回的信息,views代表着V,就是用户可以观察的视图。

    现在我们来设计用户模型

    我们只存储用户的名称、密码(加密后的)、头像、性别和个人简介这几个字段,对应修改 lib/mongo.js,添加如下代码:

    lib/mongo.js

    我们定义了用户表的 schema,生成并导出了 User 这个 model,同时设置了 name 的唯一索引,保证用户名是不重复的。
    当然你也可以自己设计自己的用户模型。

    exports.User = mongoose.model('User', {
      name: { type: 'string' },
      password: { type: 'string' },
      avatar: { type: 'string' },
      gender: { type: 'string', enum: ['m', 'f', 'x'] },
      bio: { type: 'string' }
    })
    

    小提示:mongoose 的操作就不仔细介绍,可以自行查找资料。

    用户模型有了,我们需要对模型进行操作,所有我们新建 models/users.js,添加如下代码:

    var User = require('../lib/mongo.js').User;
    
    module.exports = {
        // 注册一个用户
        create: function create(user) {
            return User.create(user).exec();
        },
        // 通过用户名获取用户信息
        getUserByName: function getUserByName (name) {
            return User
                .findOne( {name: name} )
                .exec();
        },
    };
    

    然后修改完善处理用户注册的路由,最终修改 routes/signup.js 如下:

    const fs = require('fs')
    const path = require('path')
    const sha1 = require('sha1')
    const express = require('express')
    const router = express.Router()
    
    const UserModel = require('../models/users')
    const checkNotLogin = require('../middlewares/check').checkNotLogin
    
    // GET /signup 注册页
    router.get('/', checkNotLogin, function (req, res, next) {
      res.render('signup')
    })
    
    // POST /signup 用户注册
    router.post('/', checkNotLogin, function (req, res, next) {
      const name = req.fields.name
      const gender = req.fields.gender
      const bio = req.fields.bio
      const avatar = req.files.avatar.path.split(path.sep).pop()
      let password = req.fields.password
      const repassword = req.fields.repassword
    
      // 校验参数
      try {
        if (!(name.length >= 1 && name.length <= 10)) {
          throw new Error('名字请限制在 1-10 个字符')
        }
        if (['m', 'f', 'x'].indexOf(gender) === -1) {
          throw new Error('性别只能是 m、f 或 x')
        }
        if (!(bio.length >= 1 && bio.length <= 30)) {
          throw new Error('个人简介请限制在 1-30 个字符')
        }
        if (!req.files.avatar.name) {
          throw new Error('缺少头像')
        }
        if (password.length < 6) {
          throw new Error('密码至少 6 个字符')
        }
        if (password !== repassword) {
          throw new Error('两次输入密码不一致')
        }
      } catch (e) {
        // 注册失败,异步删除上传的头像
        fs.unlink(req.files.avatar.path)
        req.flash('error', e.message)
        return res.redirect('/signup')
      }
    
      // 明文密码加密
      password = sha1(password)
    
      // 待写入数据库的用户信息
      let user = {
        name: name,
        password: password,
        gender: gender,
        bio: bio,
        avatar: avatar
      }
      // 用户信息写入数据库
      UserModel.create(user)
        .then(function (result) {
          // 此 user 是插入 mongodb 后的值,包含 _id
          user = result.ops[0]
          // 删除密码这种敏感信息,将用户信息存入 session
          delete user.password
          req.session.user = user
          // 写入 flash
          req.flash('success', '注册成功')
          // 跳转到首页
          res.redirect('/posts')
        })
        .catch(function (e) {
          // 注册失败,异步删除上传的头像
          fs.unlink(req.files.avatar.path)
          // 用户名被占用则跳回注册页,而不是错误页
          if (e.message.match('duplicate key')) {
            req.flash('error', '用户名已被占用')
            return res.redirect('/signup')
          }
          next(e)
        })
    })
    
    module.exports = router
    

    然后就是视图咯,我们来完成注册页面。新建 views/signup.ejs,添加如下代码:

    <%- include('header') %>
    <script src="http://static.runoob.com/assets/jquery-validation-1.14.0/lib/jquery.js"></script>
    <script src="http://static.runoob.com/assets/jquery-validation-1.14.0/dist/jquery.validate.min.js"></script>
    <script src="http://static.runoob.com/assets/jquery-validation-1.14.0/dist/localization/messages_zh.js"></script>
    <script>
    
    // validate signup form on keyup and submit
            var validator = $("#signupform").validate({
                rules: {
                    name: {
                        required: true,
                        rangelength: [2,10],
                        remote: {
                            url: "/signup/signupcheck",
                            type: "post",
                            dataType: "json",
                            data: {
                                name: function() {
                                    return $("#name").val();
                                }
                            }
                        }
    
                    },
                    password: {
                        required: true,
                        minlength: 6
                    },
                    repassword: {
                        required: true,
                        minlength: 6,
                        equalTo: "#password"
                    },
                    //avatar: required,
                    avatar: {
                        required: true
                    },
                    bio: {
                        required: true,
                        rangelength: [1,30]
                    }
                },
                messages: {
                    name: {
                        required: "请输入用户名",
                        rangelength: jQuery.validator.format("请输入长度在 {0} 到 {1} 之间的字符串"),
                        remote: jQuery.validator.format("{0} 已经被使用")
                    },
                    password: {
                        required: "请输入密码",
                        minlength: jQuery.validator.format("密码至少为 {0} 个字符")
                    },
                    repassword: {
                        required: "请输入密码",
                        minlength: jQuery.validator.format("密码至少为 {0} 个字符"),
                        equalTo: "两次输入的密码不一样"
                    },
                    avatar: {
                        required: "请上传头像"
                    },
                    bio: {
                        required: "请输入个人简介",
                        rangelength: jQuery.validator.format("个人简介请限制在 {0} - {1} 个字符")
                     // the errorPlacement has to take the table layout into account
                errorPlacement: function (error, element) {
                    error.appendTo(element.next());
                },
                // specifying a submitHandler prevents the default submit,good for the demo
                submitHandler: function () {
                    alert("submitted!");
                },
                // set this class to error-labels to indicate valid fields
                success: function (label) {
                    // set &nbsp; as text for IE
                    label.html("&nbsp;").addClass("checked");
                    }
                 }
            });
                
                </script>
    <style>
        .status{
            color: red;
        }
    </style>
    
    <div class="ui grid">
        <div class="four wide column"></div>
        <div class="eight wide column">
            <form class="ui form segment" id="signupform" method="post" enctype="multipart/form-data">
                <div class="field required">
                    <label for="name">用户名</label>
                    <input placeholder="用户名" type="text" name="name" id="name">
                    <p class="status"></p>
                </div>
                <div class="field required">
                    <label for="password">密码</label>
                    <input placeholder="密码" type="password" name="password" id="password">
                    <p class="status"></p>
                </div>
                <div class="field required">
                    <label for="repassword">重复密码</label>
                    <input placeholder="重复密码" type="password" name="repassword" id="repassword">
                    <p class="status"></p>
                </div>
                <div class="field required">
                    <label for="gender">性别</label>
                    <select class="ui compact selection dropdown" name="gender" id="gender">
                        <option value="m">男</option>
                        <option value="f">女</option>
                        <option value="x">保密</option>
                    </select>
                    <p class="status"></p>
                </div>
                <div class="field required">
                    <label for="avatar">头像</label>
                    <input type="file" name="avatar" id="avatar" accept="image/*">
                    <p class="status"></p>
                </div>
                <div class="field required">
                    <label for="bio">个人简介</label>
                    <textarea id="bio" name="bio" rows="5" v-model="user.bio"></textarea>
                    <p class="status"></p>
                </div>
                <input type="submit" class="ui button fluid" value="注册">
            </form>
        </div>
    </div>
    
    <%- include('footer') %>
    

    在视图中,我们使用了jQuery validate来进行客户端页面的表单验证,这样可以加深我们的使用体验。文档请参见jQuery Validate或者jQuery Validate 官网

    文档里面讲得很清楚明白,有些这些不需要和后端有交互,前端直接验证就可以搞定,比较简单。但是用户名name需要和后端交互,通过在后端检测用户名是否存在,然后返回给前端。jquery validate中提供了remote:URL方法来进行异步验证,使用 ajax 方式进行验证,默认会提交当前验证的值到远程地址,如果需要提交其他的值,可以使用 data 选项。如下:

       rules: {
                    name: {
                        remote: {
                            url: "/signup/signupcheck",    // 后台处理程序地址
                            type: "post",                  // 数据发送方式
                            dataType: "json",              // 接受数据格式
                            data: {                        // 要传递的数据
                                name: function() {
                                    return $("#name").val();
                                }
                            }
                        }
                     }
                   }
    

    这里我们简单的使用使用jQuery mockjax来模拟Ajax,具体资料自行百度。

     $.mockjax({
                url:"users.action",
                response: function (settings) {
                    var user = settings.data.name,
                            users = ["phr","asdf","zhangshan"];
                    this.responseText = "true";
                    if($.inArray(user,users) !== -1) {
                        this.responseText = "false";
                    }
                },
                responseTime: 500
            });
    

    我们使用了表单,肯定是要来出来表单的,我们使用express-formidable处理 form 表单(包括文件上传)。修改 index.js ,在app.use(flash())下一行添加如下代码:

    index.js

    // 处理表单及文件上传的中间件
    app.use(require('express-formidable')({
      uploadDir: path.join(__dirname, 'public/img'), // 上传文件目录
      keepExtensions: true// 保留后缀
    }))
    

    这样一个注册功能基本上是实现了.

    待续。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    相关文章

      网友评论

        本文标题:博客十

        本文链接:https://www.haomeiwen.com/subject/uwkjwxtx.html