我的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")}"
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容