1 前言
前面介绍了 socket.io
和 express
的使用,这节继续分析 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
文件中需要关注的几个属性type
:指定属性的类型,从源码重写typeof
可知可取值function typeof( o ) { return ( o === undefined ? 'undefined' : ( o === null ? null : Object.prototype.toString.call( o ) .split( ' ' ) .pop() .split( ']' ) .shift() .toLowerCase() ) ); }
其实就是
[object Object]
中第二个字符串的全小写形式(比如:string
,object
,array
,number
,null
,undefined
等等)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
将文件内容读取出来,然后转化成字符串,再转成对象,与指定的路由关联,保存起来(某一集合的路由名和规则文件名保持一致,比如:/user
和user.json
)。fs
是Node.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
中的数据步骤完成,简单回顾下- 加载
JSV
模块(var JSV = require( 'JSV' ).JSV;
); - 创建验证环境(
var validator = JSV.createEnvironment();
); - 加载规则文件(通过
fs.readFile( 'user.json', 'utf8', function ( err, data ) {};
回调中的data
就是我们想要的文件数据,经过JSON.parse
转成对象与指定路由对应保存起来 ); - 创建验证函数(
checkSchema = function ( obj, schema, callback ) {};
用schema
去验证obj
的合法性,然后将结果传入给回调,决定是否成功,成功就进入正常的路由处理进程)。
- 加载
PS: 从 JSV
的 Github
地址提交记录看,该插件已经五六年没更新了,但其实现方式和代码还是值得学习的。
5 模块化以及 CRUD 操作
该书中将 CRUD
操作部分进行了模块化,放到了 crud.js
模块文件中,先来看下 routes.js
中的使用,再具体看下 crud.js
中的实现部分。
在实际项目中,源码的位置大都都应该统一放到 lib/
目录下去,因此作者改变了下项目文件的目录结构(这个或许在项目起初就该定义好)
经过模块化之后, crud.js
包含了数据库连接,客户端数据验证 JSV
和 CRUD
操作三部分的代码,而路由部分就由 routes.js
专职负责。然后通过在 routes.js
引用 crud.js
来使用数据库及其操作。
至于 CRUD
代码部分就不在这贴出来了,后面的 MongoDB
系列中会有更详细的使用代码,具体与该书相关的代码,在该地址有完整版: crud.js
6 总结
该篇属于该书的 MongoDB
数据库部分,但是这里面讲述比较多的还是 JSV
这一块,后面第五节并没有讲述什么内容,因为这一块主要和 MongoDB
数据库的具体操作有关系,而这个数据这一块,我已经另开一学习系列去专门学习这个,并且基本操作部分已经完成了,待《单页WEB应用》系列完成之后就会继续完成 MongoDB
的学习。
目前在完成该篇之后,该书中的 WebServer
部分相关的三大块:Express
,Socket.IO
,和MongoDB
总算完成了,经过这段时间的学习,编码和写博客,让自己学习到不少东西。
这篇之后,接下来的一篇就是,把这三大块实际使用到该书的项目当中去,也就是聊天模块的通信,数据更新,路由等等的完成了。