NodeJS学习笔记(二)

对NodeJS的基本配置了解之后,现在需要解决一些显示问题,大致有以下问题

1、与PHP框架Laravel的密码验证系统相适应

2、异步调用的常见编程问题:Promise和EventEmitter

3、Sails框架简述

下面是详细的内容:

1、与Laravel系统的加密系统相适应

Laravel是比较流行的PHP系统,其密码加密有以下特点:(参考文章:http://www.tuicool.com/articles/zQnqyi

1)密码每次加密结果都是不同的

2)加密结果长度为60位

3)加密结果是元数据

根据文档介绍,加密采取了bcrypt函数和Blowfish算法,这是一种加盐算法,用Laravel实际运行一下代码,

Route::get(‘/‘, function(){

$str = "";

for($i=0;$i<10;$i++){

$str .= Hash::make(‘stone-fly‘)."<br>";

}

return $str;

});

结果类似如下:

$2y$10$2XweYEnSPjBBNy/EsLEVouXfE3Stq.NKIl.kNM6TB.kRokpu7hRCy

$2y$10$yK45wyX0PxWIfxhZCkxkhecTBvR/2delG9vK0XT2CE3GAWxVHZgzS

$2y$10$sSqbrX71sdUdUPcrpepcUeg5QxJ1Ow7iq7YiGrXYS7QYIRuyOoZni

$2y$10$6asY8EhViIAHk1cg6onvUO5Gc8/NDV.qyH6tObyQ5WqK5jYz89LTq

$2y$10$kpcEnWOcMvVDbGw6PqN07eL5gZholdxqatnENuD.TczAMDF1FWjpG

$2y$10$JW7fPgmebGlO1wivDmus6OeCxUGb2wefFxlGjhK5UGX0exMtIcLgO

$2y$10$yzgApGe227Yhkf6H87P7yu48KDVd0tVf0Tkv9J6KP5cEjhfU8HxGi

$2y$10$6I4yhAUpU0bBpiP6323S2e4aTtq1psI39A91fh2SDgMvJCRkEUzwC

$2y$10$EUxfsWCgbqfbQcvO0lGk8O605o2ACXe34/BqcGfeeCBNi5t4XTZdi

$2y$10$I.dA/8bdSmXWeo6BtfJB/u2pqOUVqDOv6U1ByOSypohK7B7ZpUJP.

可以看出加密了10次,各不相同,加密结果是一种元数据,原因如下:

1)头四位是加盐前缀,一般为“$2y$”、"$2a$"、"$2x$",较老的是"$2a$",最新的是"$2y$",Laravel生成的是"$2y$",或者说php5.3.7之前是"$2a$",之后是"$2y$",前者有些bug,但也可以使用,根据https://github.com/fpirsch/twin-bcrypt的说法,2a是可能正确也可能错误,2x有bug,2y肯定正确。

2)5-6位是cost值

3)7位是$,之后是22位随机生成的盐值,范围为“./0-9A-Za-z”.”

4)之后为密码

这里需要注意的是,既然每次都不同,就必须使用提供提供的api来判断密码是否正确,而不是将用户提交的密码加密然后和数据库保存的比较,这是错误的,Laravel提供了Auth::attempt()方法来做密码验证。

根据上面的描述,需要去nodejs中寻找相应的模块,在github.com上搜索"node bcrypt",按照星级排序,我尝试了前3种加密模块,分别为ncb000gt/node.bcrypt.jsshaneGirish/bcrypt-nodejsfpirsch/twin-bcrypt,它们的使用方法都差不多,大致的区别如下

    • ncb000gt/node.bcrypt.js是用c++实现的,后两者都是用js实现的
    • fpirsch/twin-bcrypt外,加密前缀都是"$2a$",fpirsch/twin-bcrypt支持3种前缀的比较,生成的密码前缀为"$2y$",当然这个并不难解决,只要替换前四个字母为"$2a$",其他两个模块就可以正常比较了

它们都提供了异步和同步操作,下面以  ncb000gt/node.bcrypt.js 为例说明一下用法:

在package.json的dependencie中加入"bcrypt":"*",然后运行npm install,或者直接运行npm install bcrypt,需要系统安装微软的编译环境,我的电脑上是visual studio 2013。

使用bcrypt也非常简单,简单示例如下

var bcrypt = require(‘bcrypt‘);

var salt = "";

var hash = "";

for (var i = 0; i < 10; i++) {

salt = bcrypt.genSaltSync(10);

hash = bcrypt.hashSync(‘stone-fly‘, salt);

console.log(hash+‘--‘+i);

}

console.log("-----------------------------------------------------------");

for (var i = 0; i < 10; i++) {

console.log(i);

bcrypt.genSalt(10, function (err,salt) {

bcrypt.hash(‘stone-fly‘, salt, function(err, hash){

console.log(hash+‘--‘+i);

});

});

}

运行结果如下

$2a$10$FwQQeqgAsB3mY53JG01xnOhQXW4jkfC/UYYVr2n0Evoc7kwxe8HXS--0

$2a$10$FYNn/8MEHfc3pRo99D2hfu0BJYHMySuRinGeGeoRjZi07w/jUW6YG--1

$2a$10$bb0f9z2LveAJnOAF.T5sRuqK7olcB6KlvcjwLQJY6MOmTv6Ys11t.--2

$2a$10$kSoUp7D3l0n4PTmTfFk/nOVVnA3jKoqjyP3mZ/hMpZ0k7dbZ4vphS--3

$2a$10$uQdp3O6toqnbmK1FNN9JH.8ck4TS13glkSr60Y6KllVnesA3Ll7i.--4

$2a$10$jjBYt8sIHo.BMOzvgoidaeyVFm4MKtnD86X3Vtn0ZkzYts0I3biVK--5

$2a$10$/Iuc1K00T6DuWvOm5oSSZuwSTgAiyyU9wMBAP.ccr9/V2yREaCQwy--6

$2a$10$JvjAdWLSc1.eLLs.E4ROsO4ztPY37FNPk8qbjv3ItntiEys0TjSzm--7

$2a$10$txYGWGYItNo4jjWkl6JPVeTO1l8tHSC3biYHsDPhCQOnVJG75DKFW--8

$2a$10$ZqvA4Jrib3z8ipP2ORELi.9Q1HvJN0x8ihMYr0uFk1DDEImoSAUge--9

-----------------------------------------------------------

0

1

2

3

4

5

6

7

8

9

$2a$10$iqLtJTN4lWwXqs7BynPDFeoeToNlAKX14aolSW85U00KOKb9zdRHy--10

$2a$10$7xRvfdsdDwT/P0EUB48lM.hCWK8UWmxbfvXE2dkZHb8093jhm3Oey--10

$2a$10$btnfDhIR00pVJyu/yEvjYO3qlB56MTNXwgfwCPeU4mkxitURqxWLW--10

$2a$10$pyLoJKmb6.FUVrd7eu9PfuvhYVnA1zheZmHJGZHJ9a5akOEL2Q46a--10

$2a$10$M/M/.CVPt2SNvtch9Yh1quhDbguuvrfPc7Je0U62O/9eM52dqDtdm--10

$2a$10$sjmoBqzAOXb2mWS9YDpwKuZqDLlN64zn5VY13xbrBoMvZV5A6MqeS--10

$2a$10$xu4sBhDh0H87PzFcZXbCGezn8qn3mnn3dIiPY4MZKcez7BzmTPvQy--10

$2a$10$TqzCq3qAVXxj5sBD4UKAKO4IK0oB4QXQpzZeC80eGIB6q3llDgx3K--10

$2a$10$tueNpGrtiH6p0NfRUfEPyOyrqHSCuxnUlfMGtsGWx5LZ/UsZX7Gp.--10

$2a$10$./m5HGNLPLjbV8fCbWaMl.3TNLK848r8eUiT2bYbzdWNvUHEeGDg6--10

研究一下上面的结果,可以看出:

  • 使用异步加密时,所有的i都是10,说明加密确实是异步进行。
  • 同步加密没什么特别的
  • 加密前缀确实是"$2a$",加盐cost是10

需要注意的是,当产生密码的时候,对应的循环值i早已经变化了,可以看出,nodejs对于批处理不适合,比如要批量插入用户信息,还要对密码进行加密,就比较麻烦,因为很难在回掉函数中调用正确的变量,因为回调函数的格式是固定的,不包含你需要的标识信息,所以异步操作不要太多,逻辑不要太复杂,否则会出现难以理解的错误。

密码比对程序如下

var bcrypt = require(‘bcrypt‘);

//laravel生成的stone-fly的加密结果

var laravel_hash = "$2y$10$2XweYEnSPjBBNy/EsLEVouXfE3Stq.NKIl.kNM6TB.kRokpu7hRCy";

//node生成的stone-fly的加密结果

var node_hash = "$2a$10$FwQQeqgAsB3mY53JG01xnOhQXW4jkfC/UYYVr2n0Evoc7kwxe8HXS";

//laravel生成的stone-fly的加密结果,将$2y$改为$2a$

var laravel_ca_hash = "$2a$10$2XweYEnSPjBBNy/EsLEVouXfE3Stq.NKIl.kNM6TB.kRokpu7hRCy";

bcrypt.compare(‘stone-fly‘,laravel_hash,function(err,result){

console.log(result);

});

bcrypt.compare(‘stone-fly‘,node_hash,function(err,result){

console.log(result);

});

bcrypt.compare(‘stone-fly‘,laravel_ca_hash,function(err,result){

console.log(result);

});

结果为

false

true

true

可以知道,密码校验只需要将laravel的加盐前缀修改为"$2a$"就可以进行正常比对了,需要注意在laravel比对时,将前缀修改为"$2y$",node校验修改为"$2a$"就可以了。

为谨慎起见,将数据库中的加密密码更新为nodejs加密的密文,然后输入用户名,密码为stone-fly,可以正常登陆,说明laravel或者说php是向下兼容的,前缀不造成影响。只需要注意nodejs的比对就可以了。

2、异步调用问题

对于nodejs来说,其优势是通过回调函数进行异步编程,可以提高前端的处理io的能力,并可以迅速释放CPU,这样可以提交性能,但是这会产生一个比较麻烦的问题,就是函数嵌套问题,即在函数执行需要前后顺序的时候,因为必须等待前面的函数执行之后才能执行后面的函数,所以不可避免的需要在回调函数中编写逻辑,如果事件过多,就会出现很难看的代码,并且也不容易编写。

设想一种场景,用户注册,大致的执行流程如下:

这种情况可能极端了一点,但是并不少见,可以看出这些操作是有先后顺序的,用nodejs实现,就会出现大量的函数嵌套,在每个函数的回调函数中调用下一个函数,看起来让人很不舒服,而且如果逻辑复杂,必须把实现封装为函数,否则就会过于复杂,很难看懂和修改。

解决这个问题方法有3种(这里主要参考朴灵的《深入浅出NodeJS》),一种是EventEmitter,一种是Promise,还有一种就是一些流程控制库,我觉得async好像更好一些,下面简单的做个说明:

1)EventEmitter:它使用nodejs的事件机制,可以处理任何异步问题,但是也有它的缺点,它本质来说相当于goto,如果代码过于复杂,也会出现很难看懂的现象

以加密为例

var bcrypt = require(‘bcrypt‘);

var EventEmitter = require(‘events‘).EventEmitter;

var emitter = new EventEmitter();

var salt = "";

var hash = "";

bcrypt.genSalt(10, function (err, salt) {

bcrypt.hash(‘stone-fly‘, salt, function (err, hash) {

emitter.emit(‘hash‘, hash);

});

});

console.log("hash=" + hash);

emitter.on(‘hash‘, function (hash) {

console.log("hash=" + hash);

});

输出为

hash=

hash=$2a$10$l9OHdYdjcaeynhnDdSL/j.2Ig1Egawc3tfx0DFJ9ouQOXZtE4b9Dq

从这里可以看出,在异步操作可以通过事件来触发,只不过上面这个例子非常简单,用函数嵌套看起来更好,也不复杂,这里只不过做个例子。如果是多个事件按顺序执行,就非常麻烦,相当于一堆的goto语句,逻辑不清晰,容易出错误。

2)Promise/Deferred

Promise/Deferred是一种规范,最简单的是Promises/A ,其实现也简单,其函数有三种结果:执行中、成功、失败,还有极端的实现,只有两种状态:成功和失败  ,其实现方式有多种,一种是使用EventEmitter封装,可以参考一下这篇文章:http://www.aspku.com/kaifa/javascript/44687.html,一种是用队列来处理,如Q模块,但对外展现的API是一样的,如下

var promise = doSomethingAync()
promise.then(onFulfilled, onRejected, onProgress).then(onFulfilled1, onRejected1,onProgress1)...

这里可以看出,要使用Promise需要将函数进行Promise模式的封装,保证其返回值是promise,想要链式处理,就需要封装更多的函数,这里就要使用队列了。不过对某些模块封装并不难,而且也可以到github.com上面搜索,基本可以满足需要。

Q模块还提供了一些专门的封装方法,可以参考现成的模块,最简单的方法如下:(来源于q-io/fs)

exports.remove = function (path) {

path = String(path);

var done = Q.defer();

FS.unlink(path, function (error) {

if (error) {

error.message = "Can‘t remove " + JSON.stringify(path) + ": " + error.message;

done.reject(error);

} else {

done.resolve();

}

});

return done.promise;

};

只要在回调函数中根据状态,调用Q.defer().reject(error)或者Q.defer().resolve()就可以了。这两个函数本质来说,就是修改内部的队列状态,promise根据状态决定调用哪个函数,再看一下promise.then()方法的源码:(来源于Q)

Promise.prototype.then = function (fulfilled, rejected, progressed) {

var self = this;

var deferred = defer();

var done = false;   // ensure the untrusted promise makes at most a

// single call to one of the callbacks

function _fulfilled(value) {

try {

return typeof fulfilled === "function" ? fulfilled(value) : value;

} catch (exception) {

return reject(exception);

}

}

function _rejected(exception) {

if (typeof rejected === "function") {

makeStackTraceLong(exception, self);

try {

return rejected(exception);

} catch (newException) {

return reject(newException);

}

}

return reject(exception);

}

function _progressed(value) {

return typeof progressed === "function" ? progressed(value) : value;

}

//去掉了一部分

//....

return deferred.promise;

};

因此,可以知道,用promise方式封装api并不难,基本是模式化的。只不过大部分情况下,可以在github.com上找到合适的。

3)流程控制库

常见的流程控制库async,step,相对来说,async更流行一些,在github.com上星级很高,它提供了20多个函数,不过我们要解决异步流程控制的问题,常见的有以下几种:

//串行执行

async.series([function(c){},function(c){},function(c){}],function(err,result){});

//异步执行

async.parallel([function(c){},function(c){},function(c){}],function(err,result){});

//串行且参数执行,前一个函数的输出结果,是后一个函数的第一个参数

async.waterfall([function(c){},function(arg1,c){},function(arg2,c){}],function(err,result){});

//自动判断依赖执行

async.auto([function(c){},function(c){},function(c){}],function(err,result){});

这里需要注意一点,回调函数c是必须的,而且必须位于每个子函数的回调函数的最后,因为async用回调函数c来判断函数是否结束了,如果位置不合适,就不会有串行的效果,如果没有回调函数,则只会执行第一个函数,因为async会认为出现了错误。我写了一个简单的例子,如下:

var async = require(‘async‘);

var fs = require(‘fs‘);

async.parallel([

function (c) {

fs.readFile(‘b.txt‘, function (err, data) {

fs.writeFile(‘a.txt‘, data, function (err) {

console.log("读取b.txt完成");

console.log("写a.txt完成");

c(null,1);

});

});

console.log(‘test‘);

},

function (c) {

setTimeout(function () {

console.log("Hello World");

c(null,2);

}, 1000);

},

function (c) {

fs.readFile(‘a.txt‘, function (err, buf) {

console.log(‘a.txt: ‘+buf.toLocaleString());

c(null,3);

});

}

],function(err, results) {

console.log("results="+results);

});

console.log(‘最后‘);

这里用async.parallel包装了3个函数,第一个函数读取b.txt的内容,写入a.txt,第二个函数等待1秒打印"Hello World",第三个读取a.txt的内容并打印。

a.txt内容为

这是a.txt的内容

b.txt的内容为

这是b.txt的内容

D:\work\nodejs\nodeStudy\study>node async3

test

最后

a.txt: 这是a.txt的内容

读取b.txt完成

写a.txt完成

Hello World

results=1,2,3

可以看出第一个函数的test最先打印出啦,之后是最后一行的"最后"打印出来,然后第三个函数执行,打印出a.txt的内容,然后读取b.txt的内容,写入到a.txt中,最后执行函数二,停顿一秒钟打印出"Hello World",最后是回调函数,回调函数在async中所有的函数都调用了之后,才会执行,当然如果遇到了错误,会停止执行函数循环,直接执行回调函数。回调函数的result是之前回调函数的第二个参数的组成的一个数组。

现在手工修改一下a.txt为原来的内容,当然不修改一个可以,修改上面的aysnc为串行,即修改parallel为series,再次执行如下:

D:\work\nodejs\nodeStudy\study>node async3

test

最后

读取b.txt完成

写a.txt完成

Hello World

a.txt: 这是b.txt的内容

results=1,2,3

可以看到头两行仍然不变,之后依次执行函数1、2、3,到函数3时a.txt的内容已经改了。

这里面需要注意的是test仍然最先打印出来,这在函数1的末尾,但是并没有执行函数2,因为async是通过回调函数来判断函数是否执行完毕,所以如果修改回调函数的位置,就会出现特别奇怪和有趣的效果。增加几个回调函数,修改内容如下

var async = require(‘async‘);

var fs = require(‘fs‘);

async.series([

function (c) {

fs.readFile(‘b.txt‘, function (err, data) {

fs.writeFile(‘a.txt‘, data, function (err) {

console.log("读取b.txt完成");

console.log("写a.txt完成");

c(null,1);

});

});

c(null,11);

console.log(‘test‘);

},

function (c) {

setTimeout(function () {

console.log("Hello World");

c(null,2);

}, 1000);

c(null,21);

},

function (c) {

fs.readFile(‘a.txt‘, function (err, buf) {

console.log(‘a.txt: ‘+buf.toLocaleString());

c(null,3);

});

c(null,31);

}

],function(err, results) {

console.log("results="+results);

});

console.log(‘最后‘);

D:\work\nodejs\nodeStudy\study>node async3

results=11,21,31

test

最后

a.txt: 这是b.txt的内容

results=11,21,3

读取b.txt完成

写a.txt完成

results=1,21,3

Hello World

results=1,2,3

可以看出,最后的回调函数执行了4次,第一次,三个函数还没来得及输出,第二次,执行了第一次执行的回调函数最近的输出,第三次,第四次依次类推,这些可以通过返回值看出来。

上面的结果虽然有趣,但是实际用起来就不舒服了,总体来说,不要使用多个async的回调函数,如果是条件跳转,可以采用多个回调函数,将返回值设为不同值,这样既保证了每个函数只执行一次async的回调函数,也能通过返回值result知道到底执行了哪个跳转。

sails框架相对来说不是很难,我研究的也不深入,以后有机会再写新的笔记吧。

时间: 2024-10-23 17:31:30

NodeJS学习笔记(二)的相关文章

nodejs学习笔记&lt;二&gt;简单的node服务器

在环境搭建好后,就可以开始动手架设(node驱动)一个简单的web服务器. 首先,nodejs还是用js编写.先来看一段node官网上的实例代码. var http = require('http'); http.createServer(function(req,res){ res.writeHead(200,{'Content-Type':'text/plain'}); res.end('Holle,nodejs'); }).listen(8080,'127.0.0.1'); console

nodejs学习笔记二链接mongodb

a.安装mongoose库用来链接mongodb数据库 安装mongodb数据库参考mongodb安装 前言(怨言) 本来是想安装mongodb库来链接mongodb的,命令行到nodejs工程目录:npm install mongodb --save 但是发现报gyp ERR! stack Error:Can't find python executable "python"错误,缺少python运行环境. 我晕死,我链接mongodb还需要Python,这不是存心让用户找虐.查到有

nodejs学习笔记二(get请求、post请求、 querystring模块,url模块)

请求数据 前台:form.ajax.jsonp 后台:接受请求并返回响应数据 前台<= http协议 =>后台 常用的请求的方式: 1.GET           数据在url中 2.POST         数据不再url中 get方式:通过地址栏来传输     name=value&name1=value1&               作用:分享 post方式:通过head头传输      数据相对安全 form action="http://www.vaidu

nodejs学习笔记二:解析express框架项目文件

上一章介绍了如何去创建一个express框架的工程项目,这章介绍一下express框架下的文件和用法解析,上一张我们创建的工程项目结构图如下: models是不属于原工程项目结构,为了实现数据模型后添加的,而node_modules这个文件夹内存放着项目需要的中间件,public是存放静态文件的文件夹,routes顾名思义就是路由解析文件的所在,views就是ejs模板引擎的视图文件,app.js是项目运行的入口存放着全局大量的配置,package.json是加载第三方包的配置文件.下面来一一解

Nodejs学习笔记(二)

Node.js 回调函数 Node.js 异步编程的直接体现就是回调. 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了. 回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数. 例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回.这样在执行代码时就没有阻塞或等待文件 I/O 操作.这就大大提高了 Node.js 的性能,可以处理大量的并发请求. 阻塞代码实例 创建一个文件 input

NodeJS 学习笔记(二)

url模块 ,包含分析和解析 URL 的工具. var url = require('url'); url.parse(urlStr[, parseQueryString][, slashesDenoteHost])  第一个参数,输入 URL 字符串,返回一个对象. var url = require('url'); url.parse('http://www.tianh.top'); { protocol: 'http:', slashes: true, auth: null, host:

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource

angular学习笔记(二十六)-$http(4)-设置请求超时

本篇主要讲解$http(config)的config中的timeout项: $http({ timeout: number }) 数值,从发出请求开始计算,等待的毫秒数,超过这个数还没有响应,则返回错误 demo: html: <!DOCTYPE html> <html ng-app = 'HttpGet'> <head> <title>18.4 $http(2)</title> <meta charset="utf-8"

Nodejs学习笔记(三)——一张图看懂Nodejs建站

前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试Nodejs>之后,代码编写环境就从Sublime转战到Eclipse下,感觉顺手多了.于是就跟着Scott老师学起了Nodejs建站的课程(推荐大家点进去看看),踏上了未爬先走的路子. 作为一个白里透白的小白来说,今天主要记录下如何用Nodejs搭建一个小小的网站,以及自己对于这种Nodejs建站的运

angular学习笔记(二十四)-$http(2)-设置http请求头

1. angular默认的请求头: 其中,Accept 和 X-Requested-With是$http自带的默认配置 2. 修改默认请求头: (1) 全局修改(整个模块) 使用$httpProvider依赖 var myApp = angular.module('MyApp',[]); myApp.config(function($httpProvider){ console.log($httpProvider.defaults.headers.common) //修改/操作$httpProv