单页WEB应用(八),WebServer Node.js 之 MongoDB

1 前言

前面介绍了 socket.ioexpress 的使用,这节继续分析 mongodb 在该书中的使用,这篇会简单的记录下 mongodb 的使用,更详细的部分会在 mongodb 系列中记录。

2 安装

安装和其它类似,通过 npm 安装就可以直接使用了,不过该书使用的应该是比较早的版本,书中代码在 mongodb 2.+ 版本中已经没法运行了,所以将会以最新版的语法修改该书中的示例代码进行代码演示

npm install mongodb --save

3 简单使用

下面使用的是最新版语法

引入模块

var MongoClient = require( 'mongodb' ).MongoClient;

上面获取到数据库客户端对象,就可以直接进行数据库连接了:

MongoClient.connect( url, callback );

callback 模版:

function ( err, db ) {

    // do sth
    // 错误处理,和数据库实例处理,如果不在改连接处处理的话
    // 那就需要将 db 缓存起来,至其他地方使用
}
  • err : 连接发生错误时的错误对象
  • db : 数据库连接实例,在连接成功后,该参数就代表连接后的数据库集合的实例,通过该实例可以操作集合的各种 CRUD 方法,下面会详细介绍。

    需要注意的是:这是连接成功后的回调的参数,如果需要在模块全局环境下操作该数据库实例,则需要单独将其缓存到全局环境下的变量中。

  • url: 地址的简要格式,mongodb://localhost:27017/spa,地址的协议头是mongodb://,第然后是数据库所在服务器地址,以及 mongodb 的默认端口号,后面跟着要连接的数据库。

  • 集合对象

    拿到数据库实例对象之后,需要指定具体操作该数据库下的哪个集合,然后通过集合对象去调用具体的 CRUD 函数,操作数据库(上面的描述不够准确,应该是这里拿到集合对象之后才能进行 CRUD 操作)

    获取集合对象:(假设,数据库 spa 下面有个 user 集合)

    var collection = db.collection( 'user' );

    很简单,通过 colletion() 函数就可以拿到对应的集合对象了。然后就可以通过 collection 去执行一些 CRUD 操作。

在《MongoDB系列》文章会更加详细的去研究下该数据库,这里就不重复贴出演示代码了。

PS: 支持 find() 之后的链式语法连接,在’Read Methods’一节:go ✈

4 数据验证

在测试过程中,经常会输入同一数据,但是结果是每次都插入到了数据库中,尝试过在插入路由中进行处理,结果不如人意,在该书中使用了一些插件来拦截和验证客户端发送来的数据,避免重复插入或恶意插入数据的操作。

4.1 JSV 验证对象

该插件基于 JSON Schema,也就是说验证规则书写方式是采用 JSON 格式

使用步骤:

  • 安装 JSV

    npm install JSV --save

    或者直接将 JSV 加对应的版本号写入到 package.json 中然后执行:

    npm install

    亦可。

    通过使用其实,JSV 的认证应该也简单,先编写好 *.json 格式的规则文件,然后通过 fs 模块将规则文件内容读取出来,转换成对象保存到对应类型的规则容器中,也就是说这个容器包含了不同路由下的不同规则对象,拿到这个对象,然后通过 JSV.createEnvironment(); 取得验证器对象:validator,通过该验证器,通过 validator.validate( obj_map, schema_map ); 将接收到的参数对象:obj_map 按照规则对象:schema_map 进行验证,结果OK,就调用回调执行服务器响应等系列操作。

    添加 JSV 验证器之后,那么服务器对客户端发送来的数据响应处理,将会被添加到 JSV 认证成功的回调中。

    关于更多关于 JSV 的相关文档和源码在 Github 地址中可找到

  • 创建 JSON Schema

    // user.json
    {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "_id": {
                "type": "string",
                "minLength": 25,
                "maxLength": 25
            },
    
            "name": {
                "type": "string",
                "minLength": 2,
                "maxLength": 127
            },
    
            "is_online": {
                "type": "boolean"
            },
    
            "css_map": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "background-color": {
                        "required": true,
                        "type": "string",
                        "minLength": 0,
                        "maxLength": 25
                    },
    
                    "top": {
                        "required": true,
                        "type": "integer"
                    },
    
                    "left": {
                        "required": true,
                        "type": "integer"
                    }
                }
            }
        }
    }

    user.json 文件中需要关注的几个属性

    1. type:指定属性的类型,从源码重写 typeof 可知可取值

      function typeof( o ) {
          return ( o === undefined 
              ? 'undefined' 
              : ( o === null 
                  ? null 
                  : Object.prototype.toString.call( o )
                          .split( ' ' )
                          .pop()
                          .split( ']' )
                          .shift()
                          .toLowerCase()
              )
          );
      }

      其实就是 [object Object] 中第二个字符串的全小写形式(比如:stringobjectarraynumbernullundefined 等等)

    2. additionalProperties:额外属性,表示是否支持额外的属性,默认是 false 在安全性考虑上这个值一般都是 false,如果允许注入其他属性,其实在阻止非法的数据插入其实毫无意义,因此使用的时候尽量给 false

      properties:这个指定了对象下面有哪些属性或成员,自身也是个对象,比如:

      {
          // 这最外层就是个对象,所以 type 为 object
          "type": "object",
          "additionalProperties": false,
          "properties": {
              // 表示最外层对象中包含的属性列表,比如,其中有个 name 属性和 appearance 对象
              "name": {
                  // 这个 name 相对于 最外层来说是个属性,但是在定义的时候要用对象来指定其
                  // 其配置
      
                  // 1. 可以指定类型
                  "type": "string",
                  // 2. 还可以指定最大长度和最小长度或值
                  "maxLength": 127,
                  "minLength": 2,
                  "required": true // 这个很明显是用来指明该属性是不是必须的
      
                  // 其他配置
              },
      
              "appearance": {
                  // 对象和最外层定义一样,type, additionalProperties, properties 等
              }
          }
      }
  • 加载 JSON Schema

    加载这一块,用到 fs 文件系统模块,通过 fs 将文件内容读取出来,然后转化成字符串,再转成对象,与指定的路由关联,保存起来(某一集合的路由名和规则文件名保持一致,比如:/useruser.json)。

    fsNode.js 自带的文件系统模块,并且同时提供了同步和异步方法,包括读写等操作。

    这里使用到了其异步读取文件函数:fs.readFile,对应的同步函数为:fs.readFileSync

    loadSchema = function ( schema_name, schema_path ) {
    
        fsHandler.readFile( schema_path, 'utf8', function ( err, data ) {
    
            objTypeMap[ schema_name ] = JSON.parse( data );
        });
    };

    上面即是加载规则文件实现,在readFile 操作成功后的回调里面,data 就是读取到的文件内容,经过 JSON.parse 转换成对象,与对应的 schema_name 保存起来。

    在加载模块的时候,执行,预先将规则保存值模块内的 objTypeMap 对象中,比如:路由 /user,保存规则文件的键:objTypeMap[ 'user' ]

  • 创建验证函数

    经过使用 loadSchema 预加载规则文件,那么验证规则就已经准备好了,现在需要一个验证器去执行这些规则,而 JSV 环境就为我们提供了这样一个认证器,其实看源码就知道,实际上就是拿到规则文件中的 JSON 转换后的对象,一一对比,然后根据每个属性和对象所设置的限制,与客户端传入的数据进行对比,最后判断是否OK。

    // routes.js
    
    // 创建验证环境
    validator = JSV.createEnvironment();
    
    checkSchema = function ( obj_type, obj_map, callback ) {
    
        var 
            // 这里拿到了验证规则对象
            schema_map = objTypeMap[ obj_type ],
    
            // obj_map 即我们要验证的对象
            report_map = validator.validate( obj_map, schema_map );
    
        // 验证结果的回调由我们控制
        callback( report_map.errors );  
    };
    

    上面代码中的 callback,就是我们需要响应验证后的数据,继续看下面

  • 验证接受的数据

    OK,一切准备就绪,验证文件,验证规则对象,验证环境,和验证函数我们都准备好了,那剩下的就是去验证了

    /user/create 路由为例,该路由是要创建新用户,那么必然要传入数据,且为包含用户名 name 等属性的对象,比如:

    {
        'name': 1111,
        'is_online': false,
        'css_map': {
            'top': 50,
            'background-color': 'rgb(136, 255, 136)'
        }
    }

    路由代码:

    // 3. 创建用户请求的路由,客户端会向服务器发送数据,因此需要用到 POST
    app.post( '/:obj_type/create', function ( request, response ) {
    
        console.log( ' create -------------> start ' );
    
        var 
            obj_type    = request.params.obj_type,
            obj_map     = request.body;
    
        // 回调中的 error_list 也就是:validator.validate 验证错误的结果
        checkSchema( obj_type, obj_map, function ( error_list ) {
    
            debug( error_list );
    
            if ( error_list.length > 0 ) {
                response.send({
                    error_msg   : 'Input document not valid',
                    error_list  : error_list
                });
            } else {
                collection = dbHandler.collection( obj_type );
    
                collection.insert( obj_map, function ( err, result ) {
                    if ( err ) {
                        console.log( err );
                        return false;
                    } else {
                        response.send( result );
                    }
    
                    // dbHandler.close();
                } );
            }
        });
    });

    根据上面定的规则文件:user.json,可知上面的创建是不会成功了,输出结果如下:

    {
      "error_msg": "Input document not valid",
      "error_list": [
        {
          "uri": "urn:uuid:b47234cb-4620-4670-bd4f-e233f773a9df#/name",
          "schemaUri": "urn:uuid:e058d31e-26b7-404b-9035-e28a4cd68424#/properties/name",
          "attribute": "type",
          "message": "Instance is not a required type",
          "details": [
            "string"
          ]
        },
        {
          "uri": "urn:uuid:b47234cb-4620-4670-bd4f-e233f773a9df#/css_map/left",
          "schemaUri": "urn:uuid:e058d31e-26b7-404b-9035-e28a4cd68424#/properties/css_map/properties/left",
          "attribute": "required",
          "message": "Property is required",
          "details": true
        }
      ]
    }

    从输出结果看,输出内容还是很详细的,一看就知道问题处在哪,首先输出的错误,是个错误对象数组,也就是 validator.validate 验证失败返回的结果,从上面的 error_list 中的 message 属性可知,name 类型不对,需要的是字符串,而第二个错误对象,说明的是缺少了 left 属性。因此完整正确的接受数据对象应该是这样的:

    {
        'name': 'lizc',
        'is_online': false,
        'css_map': {
            'top': 50,
            'left': 500,
            'background-color': 'rgb(136, 255, 136)'
        }
    }

    使用 postman 发送 post 请求,结果显示:

    {
      "result": {
        "ok": 1,
        "n": 1
      },
      "ops": [
        {
          "name": "lizc",
          "is_online": false,
          "css_map": {
            "top": 50,
            "left": 500,
            "background-color": "rgb(136, 255, 136)"
          },
          "_id": "58c64daf29b5761176aa7dac"
        }
      ],
      "insertedCount": 1,
      "insertedIds": [
        "58c64daf29b5761176aa7dac"
      ]
    }

    说明插入成功,可以通过 /user/list 路由进行确认。

    自此,通过 JSV 插件来验证 request 中的数据步骤完成,简单回顾下

    1. 加载 JSV 模块(var JSV = require( 'JSV' ).JSV;);
    2. 创建验证环境(var validator = JSV.createEnvironment(););
    3. 加载规则文件(通过 fs.readFile( 'user.json', 'utf8', function ( err, data ) {}; 回调中的 data 就是我们想要的文件数据,经过 JSON.parse 转成对象与指定路由对应保存起来 );
    4. 创建验证函数(checkSchema = function ( obj, schema, callback ) {};schema 去验证 obj 的合法性,然后将结果传入给回调,决定是否成功,成功就进入正常的路由处理进程)。

PS: 从 JSVGithub 地址提交记录看,该插件已经五六年没更新了,但其实现方式和代码还是值得学习的。

5 模块化以及 CRUD 操作

该书中将 CRUD 操作部分进行了模块化,放到了 crud.js 模块文件中,先来看下 routes.js 中的使用,再具体看下 crud.js 中的实现部分。

在实际项目中,源码的位置大都都应该统一放到 lib/ 目录下去,因此作者改变了下项目文件的目录结构(这个或许在项目起初就该定义好)

经过模块化之后, crud.js 包含了数据库连接,客户端数据验证 JSVCRUD 操作三部分的代码,而路由部分就由 routes.js 专职负责。然后通过在 routes.js 引用 crud.js 来使用数据库及其操作。

至于 CRUD 代码部分就不在这贴出来了,后面的 MongoDB 系列中会有更详细的使用代码,具体与该书相关的代码,在该地址有完整版: crud.js

6 总结

该篇属于该书的 MongoDB 数据库部分,但是这里面讲述比较多的还是 JSV 这一块,后面第五节并没有讲述什么内容,因为这一块主要和 MongoDB 数据库的具体操作有关系,而这个数据这一块,我已经另开一学习系列去专门学习这个,并且基本操作部分已经完成了,待《单页WEB应用》系列完成之后就会继续完成 MongoDB 的学习。

目前在完成该篇之后,该书中的 WebServer 部分相关的三大块:ExpressSocket.IO,和MongoDB 总算完成了,经过这段时间的学习,编码和写博客,让自己学习到不少东西。

这篇之后,接下来的一篇就是,把这三大块实际使用到该书的项目当中去,也就是聊天模块的通信,数据更新,路由等等的完成了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若叶岂知秋vip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值