http://blog.csdn.net/yanghua_kobe/article/details/17199417
項目簡介
這是一個資產管理項目,主要的目的就是實現對資產的無紙化管理。通過為每個資產生成二維碼,來聯合移動終端完成對資產的審核等。這個項目既提供了Web端的管理界面也提供移動端(Andorid)的資產審核、派發等相關功能。
我們用Node.js構建該項目的Web端以及移動端的Serveice API。
項目主框架:Express 簡介
Express 是一個非常流行的Node.js的web框架。基於connect(node中間件框架)。提供了很多便於處理http請求等web開發相關的擴展。
Express簡單的結構圖:
Express的特性:
- 基於Connect構建
- 健壯的路由
- 提供豐富的HTTP處理方法
- 支持眾多視圖模板引擎(14+)
- 內容協商
- 專注於提供高性能
- 環境基於配置
- 快速構建可執行的應用程序
- 高測試覆蓋率
前端框架簡介
Bootstrap
Bootstrap是Twitter推出的一個用於前端開發的開源工具包。它由Twitter的設計師MarkOtto和JacobThornton合作開發,是一個CSS/HTML框架。Bootstrap是簡潔、直觀、強悍的前端開發框架,讓web開發更迅速、簡單。
同時,很多基於Bootstrap的開源插件也讓Bootstrap社區更加活躍。
最新的Bootstrap3提供了非常強的定制化特性。包括Less,jQuery插件等。
Bootstrap 為您提供了所有這些基本的模塊- Grid、Typography、Tables、Forms、Buttons和Responsiveness。
此外,還有大量其他有用的前端組件,比如Dropdowns、Navigation、Modals、Typehead、Pagination、Carousal、Breadcrumb、Tab、Thumbnails、Headers等等。
有了這些,你可以搭建一個Web 項目,並讓它運行地更快速更輕松。
此外,由於整個框架是基於模塊的,你可以通過定制你自己的CSS來使得它滿足你的特殊需求。
它是基於幾種最佳實踐,我們認為這是一個很好的開始學習現代Web 開發的時機,一旦你掌握了HTML 和JavaScript/jQuery 的基本知識,你就可以在Web 開發中運用這些知識。
Ember.js
Ember.js是一個JavaScript的MVC框架,它由Apple前雇員創建的SproutCore2.0改名進化而來。
構建一個Ember應用程序,通常會使用到六個主要部件:應用程序(Application)、模型(Model)、視圖(View)、模板(Template)、路由(Routing)和控制器(Controller)。
這里我們server端主要依賴express框架,它提供的這些功能跟express有些是相同的。我們主要應用了Ember的模板組件,Express對於它提供了很好的集成。我們只需要進行很簡單的配置即可:
- app.set('view engine', 'html');
- app.set('views', path.join(__dirname, 'views'));
- app.set("view options", {layout : false});
- app.register('.html', require('ejs'));
測試框架簡介
should.js
should 是用於node.js的一個表述性、可讀性很強的測試無關的“斷言”庫。它是BDD風格的,用一個單例的不可枚舉的屬性訪問器擴展了Object的prototype,允許你表述對象應該展示的行為。
should的一個特性是可以支持鏈式斷言,比如:
- user.should.be.an.instanceOf(Object).and.have.property('name', 'tj');
- user.pets.should.be.instanceof(Array).and.have.lengthOf(4);
mocha
mocha + should.js
項目組件 - node modules
mysql
功能簡介:MySQL- node.js平台MySQL驅動,支持事務、連接池、集群、sql注入檢測、多做參數傳遞寫法等特性。
主頁地址:https://github.com/felixge/node-mysql
eventproxy
功能簡介:eventproxy- node.js 異步回調代理。主要用來解決node中深層次回調嵌套的問題,支持很多異步模式:多類型異步、重復異步、持續型異步。
主頁地址:https://github.com/JacksonTian/eventproxy
validator
功能簡介:javascript的驗證工具集,支持兩種模式:check(校驗)/sanitize(處理),同時提供了可擴展的錯誤處理。
主頁地址:http://github.com/chriso/node-validator
ejs
功能簡介:embered.jsjavascript 模板引擎(可以跟express集成,作為服務端模板引擎)
主頁地址:https://github.com/visionmedia/ejs
loader
功能簡介:loader- 資源加載工具,可以區分開發模式、發布模式;在發布模式下可進行資源壓縮、合並。以實現減少靜態資源帶寬並且便於實現客戶端緩存
主頁地址:https://github.com/TBEDP/loader
canvas
功能簡介:canvas - node.js 常用的圖形圖像處理庫,是很多其它庫的基礎依賴庫
主頁地址:https://github.com/learnboost/node-canvas
captchagen
功能簡介:captchagen-node.js常用驗證碼圖片處理庫,依賴上面的canvas庫
主頁地址:http://github.com/wearefractal/captchagen
crypto-js
功能簡介:crypto-js- javascript 常用加密庫、hash庫封裝,支持sha-x / md5 / hash等各種加密、hash算法
主頁地址:http://github.com/wearefractal/captchagen
nodemailer
功能簡介:nodemailer- 郵件發送工具,支持SMTP等郵件發送協議
主頁地址:http://github.com/andris9/nodemailer
qrcode
功能簡介:qrcode- node.js服務端的qrcode生成器。支持多種輸出類型(dataUrl/file/bitArray)
主頁地址:http://github.com/soldair/node-qrcode
pdfkit
功能簡介:qrcode- node.js服務端的qrcode生成器。支持多種輸出類型(dataUrl/file/bitArray)
主頁地址:http://github.com/soldair/node-qrcode
excel
功能簡介:excel- node.js excel解析器,支持xlsx(Excel2007+)
主頁地址:https://github.com/trevordixon/excel
excel-export
功能簡介:excel-export- node.js excel生成器,支持導出excel
主頁地址:https://github.com/functionscope/Node-Excel-Export
net-ping
功能簡介:net-ping- node.js 對ping的封裝,用於測試目標主機是否可達
主頁地址:https://bitbucket.org/stephenwvickers/node-net-ping
debug
功能簡介:debug- node.js debug工具,對console.log的封裝,支持多種顏色輸出。
主頁地址:https://github.com/visionmedia/debug
項目組織結構
NPM - Node.js 模塊依賴管理工具
npm是管理node.js模塊依賴的工具,依賴於開源技術的優勢就是你有非常多的優秀庫可以幫助你快速構建一個系統,但就像一把雙刃劍,由於開源導致版本的升級不可控。這時,一個集中性的模塊依賴管理工具的優勢就十分明顯。它負責幫你管理開源項目的版本,你只需要添加對某個開源模塊的依賴即可。
unix/Linux下安裝npm:
- curl http://npmjs.org/install.sh | sudo sh
如何在項目中使用npm管理你的依賴:
(1)在項目的根目錄下創建一個package.json文件
在dependencies下添加所需要依賴的模塊,示例如下:- $ cd projectPath
- $ npm install
這時你會發現,項目的根目錄下多了一個node_modules文件夾,那里面就是從npm遠程庫里下載的模塊然后“安裝”到你的項目中的。
現在,你就可以在你的項目中應用你依賴的這些modules了。你可以通過require關鍵字來使用他們。比如,
- require("eventproxy");
Node.js 模塊加載機制
node.js的模塊加載基於CommonJS規范。
在Node.js中,將模塊分為兩大類:
(1)原生模塊
原生模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,加載速度最快。
(2)文件模塊
node.js依賴modulepath(模塊路徑)來加載module,而modulepath的生成規則主要是從當前文件目錄開始查找node_modules文件夾,然后依次進入父目錄查找父目錄下的node_modules目錄直至到根目錄下得node_modules目錄。所以在require的時候,如果帶上module的路徑,則按照該路徑查找,如果沒有就按照上面的node_modules文件夾向上追溯查找,如果都沒有找到,則拋出異常。
自動化部署
項目環境的構建、部署都是自動化的。
我們假設項目最終會發布在任意版本的Ubuntuserver上。在安裝Git的前提下,通過如下命令去clone項目到本地:
- git clone git://github.com/yanghua/FixedAssetManager_Server.git
- node_install_ubuntu.sh - 在ubuntuserver上安裝node.js的腳本
- node-canvas-install_ubuntu.sh - 在Ubuntuserver上安裝node-canvas的腳本
- mysql_install_ubuntu.sh - 在Ubuntu server上安裝mysql的腳本
- dispatch.sh - 部署項目的腳本
- 內置的負載均衡器(使用nodecluster module)
- 以守護進程運行
- 0s(不間斷)重啟
- 為ubuntu/ CentOS 提供啟動腳本
- 關閉不穩定的進程(避免無限死循環)
- 基於控制台監控
- HTTP API
- 遠程控制以及實時監控接口
異常監控與郵件推送
node.js 到處都是異步調用。常用的try/catch同步捕獲異常並處理的方式,在這里不起作用了。這是因為很多callback已經離開了當時try的上下文,導致無法獲取異常產生的堆棧信息。基於這個問題,我們對異常處理的模式按類型進行區分處理:
(1)http請求異常
這種異常Express就可以進行處理。如果是非法請求,在路由的時候,對未匹配的請求進行統一處理:
- app.get("*", others.fourofour);
(2)業務異常
這種異常通常不會影響到程序的運行,我們以不同的異常代碼返回給前端或者終端,來給調用端友好的提示。
(3)應用程序級別的異常或必須處理的錯誤
這種情況下,應用程序可能沒有辦法處理異常,也有可能由應用程序拋出。對於這種應用程序級別的異常。我們用兩種方式來catch:
[1]利用Express提供的應用程序的異常處理機制:- app.error(function(err, req, res, next) {
- mailServie.sendMail({
- subject : "FixedAssetManager_Server[App Error]",
- text : err.message + "\n" + err.stack + "\n" + err.toString()
- });
- if (err instanceof PageNotFoundError) {
- res.render("errors/404");
- } else if (err instanceof ServerError) {
- res.render("errors/500");
- }
- });
[2]應用程序已經無法響應處理了,則利用node.js提供的,對於進程級別的異常處理方式:
- process.on("uncaughtException", function (err) {
- mailServie.sendMail({
- subject : "FixedAssetManager_Server[App Error]",
- text : err.message + "\n" + err.stack + "\n" + err.toString()
- });
- });
靜態資源優化:壓縮合並與緩存
web應用中對於資源的定義大致分為:靜態資源、動態資源兩種。動態資源通常是可變的,需要進行相應處理的,而靜態資源在線上通常都是不會變的。常見的靜態資源有:javascript文件、css文件、圖片文件等。對於這些靜態文件,我們通過設置過期時間來進行緩存。而對於文本文件,由於瀏覽器的解析行為,對他們進行合並或者壓縮都不會產生影響。
這里需要提到我們在組件中介紹的Loader。在項目剛被clone下來的時候,需要先執行makebuild來對項目進行初始化。在初始化的過程中,Loader會對項目的views文件夾中的文件進行掃描。它通常會掃描html界面:查找類似於如下的片段:
- <!-- style -->
- <%- Loader("/public/stylesheets/login.min.css")
- .css("/public/libs/bootstrap/css/bootstrap.min.css")
- .css("/public/stylesheets/login.css")
- .done(assets)
- %>
- <!-- script -->
- <%- Loader("/public/libs/js/login.min.js")
- .js("/public/libs/jquery/jquery-1.10.2.min.js")
- .js('/public/libs/bootstrap/js/bootstrap.min.js')
- .js("/public/libs/CryptoJS_v3.1.2/rollups/sha256.js")
- .js("/public/libs/js/login.js")
- .done(assets)
- %>
- //config for production env
- app.configure("production", function () {
- app.use('/public', express.static(staticDir, { maxAge: maxAge }));
- app.use(express.errorHandler());
- app.set('view cache', true);
- });
Restful風格的URL
Restful以“Resource”為核心概念,認為URL是用來表示一種資源。而不應該表示一個動作或者其他的東西。而動作,比如“CRUD”正好對應http的四個method:get/post/put/delete。本項目中,我們大部分的URL以Restful風格為主,但沒有嚴格貫徹執行。
前端內容模板化、組件化
前端我們采用的是ejs的模板來構建,它很好得實現了html的片段化、組件化。有一個基礎的模板,別的都只是一塊html片段。它們在服務端完成組合、解析,生成完整的html流輸出到客戶端。
這樣的開發模式,使得前端代碼的划分比較清晰,組件化也使得代碼的復用變得更容易。
makefile
在項目初始化的過程中,我們使用makefile文件來使得一些動作自動化運行。比如我們之前提到過的構建assets.json來合並文件的動作,就是通過執行makebuild文件來完成的。
增強的Debug模塊
目前,Node.js還沒有很強大的調試工具。常用的輔助診斷方式就是打log。但繁多的日志輸出,混雜在http log里實在是不方便判斷。我們在項目中使用了debug module來進行debug,他支持對log加不同顏色的key word並且還支持timestamp。你在一大堆日志中,一眼就足以區分是從哪個module或者組件輸出的。我們在項目中對不同的layer應用不同的關鍵字:
- var debug4Ctrller = require("debug")("controller");
- var debug4Proxy = require("debug")("proxy");
- var debug4Lib = require("debug")("lib");
- var debug4Test = require("debug")("test");
- var debug4Other = require("debug")("other");
將其置為全局:
- global.debugCtrller = debug4Ctrller;
- global.debugProxy = debug4Proxy;
- global.debugLib = debug4Lib;
- global.debugTest = debug4Test;
- global.debugOther = debug4Other;
- debugCtrller("XXX %s", "YYY");
這樣在Terminal中,輸出的log會按照不同的顏色進行區分,辨別性明顯增強:
一切都可自動化——Grunt
為什么需要任務運行器?
為什么使用Grunt?
- npm install -g grunt-cli
2:在項目的根目錄下新建一個Gruntfile.js文件,該文件為grunt的配置、初始化文件
- npm install grunt --save-dev
依賴會被自動寫入package.json的devDependencies項中。
關於Gruntfile的編寫規則,詳細請查看, Gruntjs中文文檔。- jshint: 用於對JS語法進行強制檢查
- csslint: 用於對css語法進行強制檢查
- uglify: 用於壓縮項目文件
統一的代碼風格
自動格式化工具
- alignment:等號對齊排版插件
- JSFormat:JS代碼格式化排版工具
- HTML-CSS-JS Prettify 代碼格式化工具
你只需要在項目的根目錄下,創建一個.jsbeautifyrc文件,里面對縮進,空格等進行定義即可覆蓋默認配置。這非常方便那些已經習慣了自己有一套代碼風格的人使用這些插件。
更難能可貴的是,對於一個項目你可以有多個.jsbeautifyrc文件進行配置。他們的優先級取決於這些配置文件靠近待格式化文件的程度(某種意義上就是這些配置文件在目錄層次的深度)。這非常切換我們的需求:因為node項目前后端都是js。對於后端我們采用的是4空格縮進,對於前端JS我們采用的2空格縮進。那么我們只需要在前端JS文件夾下,新建一個新的.jsbeautifyrc配置文件,copy上面的配置,然后將indent_size修改為2即可。強制檢查工具
自動格式化工具只是一種“效率工具”,不足以形成“強制規定”。這里我們輔以代碼檢查工具,來強制要求代碼風格、語法規范。
檢查工具在GruntSection已經列出,在commit代碼之前,必須運行檢查,並確保沒有任何Warnning跟Error。Express 2.x to 3.x 接口適配與調整
原先的模板配置方式是采用在html文件,設置:
- <% layout('layout') -%>
來標識一個component會套用某個模板,而在3.x中ejs模板引擎,改為采用middleware的方式使用:(需要安裝一個module:express-partials)
- app.use(require('express-partials')());
與此同時,3.x專門提供了一個設置引擎的接口:
- app.engine('html', require('ejs').renderFile);
替代了原先2.x的:
- app.register('.html', require('ejs'));
2、處理錯誤的方式改變:
2.x處理錯誤有專門的一個API:
- app.error(function(err, req, res, next) {
- //error logic handle
- };
3.x退而采用middleware的方式來處理:
- app.use(function(err, req, res, next) {
- //error logic handle
- }
更多express2.x to 3.x的改變,可以看看官方給出的 變更列表。
從EventProxy 到 async 切換
eventproxy是淘寶前端團隊開發的一個node.js事件處理代理。用於輔助開放人員組織代碼的執行順序,對於很多需要干預執行順序與過程的代碼,避免了node.js深層嵌套的callback模式。
async跟eventproxy出於同樣的目的。但在API的設計模式上有所差異。async的API的風格偏向於“整合”,Eventproxy偏向於“拆分”。Mongodb for node.js:mongoose的使用
model的定義
MongoDB里數據的集合稱之為collection。而每個collection都有一個schema與之對應,可以簡單的理解為是對其數據的定義(類型與結構)。
對應到mongoose里,一個schema是一個model,形如:給每個層定義索引文件
程序設計的一個重要指標:模塊性。在c/c++里有頭文件,在面向對象語言里有pagckage/namespace的概念。他們的目的之一就是提升模塊性,降低耦合度。
在node.js中,我們也可以采用類似c/c++的headfile的模式,以層為單位。將對外可見的以文件為單位的module以一個獨立的文件對外開放(通常我們稱其為index.js文件)。形如:supertest 模擬 http request 測試
supertest是一個用於模擬http request的module,可借助其進行web功能測試。
它提供了基於描述的API鏈式調用,可以非常容易得模擬http請求測試,形如:密碼采用混入salt值的方式進行加密
密碼只是采用hash方式進行“加密”,還是相當不安全的。隨着現在計算能力的增強以及字典規模的擴大,簡單的md5已經非常不安全。一旦被拖庫,密碼很容易就會被破解。關於密碼的問題,除了采用(SSL加密數據傳輸鏈路)一直都沒有非常成熟的解決方案。所以,問題就退而求其次轉變為如何提升破解難度的問題。而在密碼中混入salt,是一直非常經濟而有效的方式。這里我們處理用戶身份認證的方式是:
入庫之后的加密密碼 = sha3 (md5 (passwrod) + salt)
其中:salt的計算方式為:sha256(userName)解決linux解壓縮包中文亂碼
在windows上打包的zip壓縮包,在ubuntu上解壓縮后,凡是文件名含有中文的都出現了亂碼。產生這一問題的原因是:在windows上壓縮文件,通常采用系統默認編碼(通常是gbk或gb2312),而傳到Linux上去,在linux上通常都默認采用的utf8編碼,所以需要進行解碼。
項目中有需要在服務器上對上傳上來的zip壓縮包解壓縮的步驟,默認調用的是shell命令(unzip命令)。網上很多提供的解決方案是通過提供"-O"參數,顯示指定編碼。但未能成功,因為在現在版本的unzip里,該參數已經失效了。通過unzip幾經折騰,還是無法解決亂碼問題,於是轉而采用7z來進行解壓縮,並顯式指定英文環境ASCII編碼(通過LANG=C),示例:- LANG=C 7z e `source zip file path` -o`target path`
其中參數“e”表示釋放所有文件到目標路徑(遞歸所有壓縮包中的子文件夾),參數“-O”指定解壓到的路徑(注意參數-O跟輸出路徑中間無空格)。
這一步只是完成了解壓,文件名這時還是亂碼的。此時需要linux上專門的轉碼工具——convmv來進行編碼轉換!
如果該命令不存在,可以先apt-getinstall一下,然后運行如下命令:- convmv -f cp936 -t utf8 -r --notest -- `target path`/*
該命令的意思是:對`targetpath`下的所有文件,從cp936編碼轉換成utf8編碼,其中的--notest表示不進行測試,直接轉換。如果你想確保轉碼安全,可以先進行測試,看是否會產生亂碼,然后再進行轉碼,測試命令如下:
- convmv -f cp936 -t utf8 -r -- *
這是你只要關注文件名是否會產生亂碼,無需關注目錄名,如果沒有亂碼,就可以安全轉換了。
如果文件內容有亂碼,可以借助如下命令對文件進行轉碼:- iconv -f cp936 -t utf8 -o output.txt input.txt
備注:我這邊遇到的是,在一個壓縮包內部有文件夾的情況下,在windows壓縮,在ubuntu下解壓縮產生了中文亂碼。但如果只是選中幾個文件直接壓縮,在ubuntu下不做轉碼,中文照常顯示。