我的node模块备忘录

最近工作中积累了不少模块的使用办法,特此备忘啦,哇咔咔


TCP服务器

TCP服务器需要用到net模块,建立TCP服务器的场景还是很多的,很多硬件发送帧都需要TCP服务器来接收,学会这个还是很实用,我用的方法也不多,大致为:

  • 创建服务器: createServer
  • socket连接建立
  • socket处理
  • socket连接关闭

直接上代码

var net = require('net');

//这里需要注意,直接写回环地址,比如localhost,或者127.0.0.1,就只会监听本机发来的连接,
//写成0.0.0.0 就可以处理发送到本机的所有连接了
var HOST = '0.0.0.0';
var PORT = 3030;

//创建tcp服务器,注意末尾需要把host和port加上,表示监听本机3030端口
net.createServer(function(sock) {

    // 输出我们获得的连接
    console.log('CONNECTED: ' +
        sock.remoteAddress + ':' + sock.remotePort);

    // 这是socket实例数据处理的一个事件,收到的数据会进入这里,然后交给回调函数的第一个参数
    sock.on('data', function(data) {
        //你可以在这里处理这个数据,做点什么事吧
    });

    // 这是socket实例关闭连接的一个事件
    sock.on('close', function(data) {
        console.log('CLOSED: ' +
            sock.remoteAddress + ' ' + sock.remotePort);
    });

}).listen(PORT, HOST);//不要忘记这里

//加个信息提示一下
console.log('Server listening on ' + HOST +':'+ PORT);

HTTP服务就不说了,做网站的开不了HTTP服务器,还是先去看看框架吧


Request模拟报文发送

Request我现在多用于测试自己写的api,大致使用:

  • get方法
  • post方法

还是直接上代码

var request = require('request');

///这里就是设置一下参数了
var options = {
    //url不必说了,发向那个就写那个,这里是我的一个例子
    url: 'http://localhost:3000/commodityManage/purchaseAdd',
    //报文头,这里就是按需填写了,我因为希望发送和接收都是json格式,所以这么写
    //一般为了安全会在报文头加token,这里你也是可以模拟的,可以去浏览器抓取请求报文,能理解的更深一些
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    },
    //这里就是填写数据了,我这里使用的是post方法,get的话是不用加的,json格式大家应该看的懂吧
    form: {
        'commodityList': [
            {
                'commodityName': '坦克杯',
                'commodityId': '4',
                'commodityPrice': 79999,
                'commodityNumber': 1
            },
            {
                'commodityName': '飞机杯',
                'commodityId': '5',
                'commodityPrice': 128,
                'commodityNumber': 1
            }
        ],
        'purchasePrice': 79999,
        'userId': '2'
    }
};

//这里为了方便写了一个函数,大家也可以直接放在下面,但是不推荐这么做
//在这里做了一个检测,当request抓取到error时,会传进回调函数的第一个参数,而第二个参数是返回报文
//第三个就是返回的信息了。200是正常处理的状态,所以进行一个显示操作
function callback(error, response, body) {
    if (!error && response.statusCode == 200) {
        var info = JSON.parse(body);
        console.log("info:", info);
    }
}

//这里也要注意,post需要这么写,使用get时,直接写request(options, callback);
request.post(options, callback);

当然,这是给http服务器发送报文,tcp发送报文的方式在net模块中有,非常简单,不再赘述


Buffer处理

Buffer处理也是我跟硬件打交道需要掌握的,硬件一般走tcp来传输数据,传输到的数据一般都是流式数据
这次工作我主要接触的是16进制的buffer,所以我大致用到的方法:

  • buffer转json
  • json转字符串

这次处理也是比较闹心,比较硬件给的buffer不是那么听话,所以先用JSON.stringify()来统一格式,然后转成字符串数组,但是这么做出了一个新问题,转出来的是10进制的,而我需要多16进制的字符串进行解析,所以,就多一个10进制转16进制的步骤,代码如下

var dataPromise_1 = JSON.stringify(data);
var dataPromise_2 = JSON.parse(dataPromise_1);
var array = dataPromise_2.data;
var str = '';
for(var num = 0; num < array.length; num++) {
     //这里因为10进制中,01变成了1,为了还原,所以有了这个步骤
     if(array[num] < 16) {
          str += '0' + array[num].toString(16);
      } else {
          str +=  array[num].toString(16);
      }
}

也许有人说,为什么不用toString(),硬件传过来的buffer编码布吉岛方式呀,心里苦,只能傻傻的这么暴力解决了


一些关于时间处理的函数

这次也是大大刷新了我对时间函数的用法,js提供的date对象,真是太好用了,不需要其他的模块,已经非常强大了
看api手册就能获得大部分信息,平时用chrome的控制台也能补全函数,不怕忘记,说几个这次遇到的问题

  1. 时区对齐
    服务器直接获取客户端的时间对象,可能会出现在原有基础上加上8小时的问题,这个情况出现的原因是,我们国家统一采用北京所在时区的时间,而我国幅员辽阔,横阔多个时区,避免时区带来的混乱,所以有了这8小时的误差,但是我们在开发中,很多时候也是使用北京时间,所以就得消除8小时误差带来的影响
date.setHours(date.getHours() + date.getTimezoneOffset() / 60);//这样来消除
  1. 获得当前星期是一年中的第几个星期
    一年中的星期数是有限的,对他们一一编号,在做时间选择的时候,是非常方便的,方法也非常简单
function getWeek(date, callback) {
    var time,week,checkDate = new Date(date);
    checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
    time = checkDate.getTime();
    checkDate.setMonth(0);
    checkDate.setDate(1);
    week=Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
    callback(week);
}

3.关于时间选择器
大家应该都知道datetimepicker,我们可以通过这个选取两个时间点,来获取这两个时间点之间的数据,但是这里需要注意一点,我们在时间选择器上选择了年月日形式的两个时间点,比如2016年4月1日到2016年4月3日,我们想获取的是1号,2号和3号的数据,但是时间选择器的终止时间是2016年4月3日 00:00:00,这样就只能获取1号和2号的数据了,所以需要将时间拨快一天,这里就用到setDate这个方法

date.setDate(date.getDate() + 1);

登录相关的模块补充

登录相关的模块在我的另外一篇文章中系统的讲过了
点击这里查看
这里做一个小小的补充,就是我用到的加密模块bcrypt-nodejs
我们一般在数据库保存的是密文密码,不是明文密码,密码学是一个专门的学问,有兴趣的可以深入参考一下,这里我只说用法

var bcrypt = require('bcrypt-nodejs');
var password = user.password;//用户传入的明文密码
var hash = bcrypt.hashSync(password);//保存这个就行了

获取url的参数

我现在一般使用两种方法来在url里添加参数,所以,后台获取参数也会有所不同

  1. 在url加入json字符串
    这应该是很常见的方法,前端js可以在请求的url里加上json字符串,格式还可以自己定义,只要前后端保持一致就行,node在后台的解析也很方便,不用自己写正则表达式了
var url = require('url');
var token = (url.parse(req.url, true).query.token;//假设我传入的参数名是token
  1. 路由参数
    使用过express的童鞋应该知道这个,在参数比较少的情况下,这个东西也是很好用的,它不需要额外的模块来解析
var express = require('express');
var router = express.Router();
//路径后面加上:,再跟上的就是参数名
router.get('/index/:id', function(req, res, next) {
   //参数名保持一致,就能从params里取到参数的值了
  var id = req.params.id;
});

生成唯一的短Id

有的时候,做唯一性标识,而且不希望太长的时候,这是个很方便的模块

我自己生成的短Id

大家可以算算,你的数据量有多大时,会出现重复

var shortid = require('shortid');
var appId = shortid.generate();

还有很多用法自行google


文件上传

由于我使用的是express,官方推荐的中间件就是multer,这个东西确实不错,你不用这个,直接在req里是取不到文件的
使用它,有几个点要注意

  1. form表单必须有enctype="multipart/form-data" ,而且提交方式为post
  2. 多文件上传,input必须带上multiple="multiple",否则默认只能传一个文件
  3. 后台对于单文件和多文件的处理是不同的,单文件的路径保存在file里,多文件保存在files里
var multer = require('multer');

var appStorage = multer.diskStorage({
    destination: function(req, file, callback) {
        //存放的位置
        callback(null, 'public/images');
    },
    filename: function(req, file, callback) {
        var appId = req.params.appId;
        console.log('appId', appId);
        var fileFormat = (file.originalname).split(".");
        //这一步会将存放的文件重命名成你想要的名称
        callback(null, file.fieldname + '-' + appId + '.' + fileFormat[fileFormat.length - 1]);
    }
});

//单文件使用single,里面的参数必须和input里的name一致
var startUpload = multer({ storage: appStorage}).single('startImages');

//多文件使用array,里面的参数必须和input里的name一致
var carouselUpload = multer({ storage: appStorageArray}).array('carouselImages');

//单文件处理
startUpload(req, res, function (error) {
        if (error) {
            //错误处理
        } else {
           //注意是file
            console.log(req.file.path);
        }
});

//多文件处理
carouselUpload(req, res, function (error) {
        if (error) {
            //错误处理
        } else {
           //注意是files,这里保存的是一个数组
            console.log(req.files[0].path);
        }
});


异步编程

讲真,node自带并发太折磨人了,写一个for循环,竟然每个循环体都是同步进行的,一旦有数据相关,for循环都不能用,一般方法是写回调,但是回调太多就成了大括号陷阱了,所以还是要借助模块来帮忙,所以,我目前是采用两种方法解决异步编程

  • 数量少写回调
  • 数量多用async

简单说一下async我用的用法

var async = require('async');
var taskList = [task_1, task_2, task_3];
async.eachSeries(taskList, function(item, callback_async) {
        //item里面有taskList的值,用它可以来取值
       //做点什么吧,接下来的回调会进入下一个任务
      callback_async(null, item);
    }, function(err) {
        //错误处理
        console.log(err);
        if(err) {
            callback({ success: false, errorMessage: err});
        } else {
            callback({ success: true});
        }
});

有的时候会发现async也比较麻烦,那么推荐另外一个库co
co配合yield(es6),可以达到异步的目的

const co = require('co');

function* task_1() {
  // 你的函数
}

function* task_2() {
  // 你的函数
}

function* task_3() {
  // 你的函数
}

co(function *() {
  yield task_1;
  yield task_2;
  yield task_3;
}).then().catch();

闭包

node本来就是js,说到js就得说说闭包呀,其实什么是闭包这个问题也是比较难理解的
阮一峰的网络日志中有这么一个解释

闭包就是能够读取其他函数内部变量的函数。

那么,为什么要闭包?
我们不可能把变量都设置为全局变量,在函数中的变量,我们也有取出来的需求,在java的类中,有get方法可以直接获取,而我们闭包所做的,也就类似于这个了。

//Java
public class init(){
     private String username;    
}

public String getUsername() {
   return username;
 }
//js
function init() {
  var name = "Mozilla"; 
  function displayName() { 
    alert(name);  
  }
  displayName();    
}

原型

说到原型还是得把Java拿出来做对比,Java的类继承模型非常典型,而Js的则是饱受非议的原型继承
Java的类继承不是重点,但是要理解原型最好还是参考一下Java,有差异才有比较
对于Js,我还是习惯使用栗子,在Js的array对象中,是没有最大值,最小值的方法的,我们可以通过原型来精简我们的代码

Array.prototype.max =
function(){ 
  return Math.max.apply({},this) 
} 

Array.prototype.min = function(){ 
  return Math.min.apply({},this) 
} 

[1,2,3].max()// => 3 
[1,2,3].min()// => 1

这样数组就直接可以取最大值最小值了,这样相当于重写原型中的方法(虽然原本没有)

原型的用法还很多,我们看看如何把js的对象做的和java的差不多
方法一:

var init = function(username) {
    this.username = username;
};

init.prototype = {
  getName: function() {
      return this.username;
  },

  setName: function(username) {
      this.username = username;
  }
};

var test = new init();
init.setName("fghpdf");
init.getName(); // => "fghpdf"

方法二:

var init = function(username) {
    this.username = username;
};

init.prototype = {
  getName = function() {
      return this.username;
  },

  setName = function(username) {
      this.username = username;
  },
  return {
       getName: getName,
       setName: setName
  }
};

var test = new init();
init.setName("fghpdf");
init.getName(); // => "fghpdf"


proto) 属性

该属性可以获取或设置一个对象的原型

  1. 创建一个以指定对象为原型的对象
var obj = {
    __proto__: myProto,
    foo: 123,
    bar: "abc"
};
  1. 为内置类型添加子类型
var MyArrayProto = Object.create(Array.prototype);
//还可以写成var MyArrayProto = {__proto__:Array.prototype};
MyArrayProto.foo = function (...) { ... };
function createMyArray() {
    var arr = Array.prototype.slice.call(arguments);
    arr.__proto__ = MyArrayProto;
    return arr;   
}
var myarr = createMyArray(1,2,3);    //myarr会有foo方法,也会有其他的数组方法

函数序列化

函数自带toString方法,可以把函数转成字符串

function a() { 
  console.log("aaaa")
};
a.toString(); // => "function a(){console.log("aaaa")}"

推荐阅读更多精彩内容