小馬的大前端之路——Node.js初探

歡迎關注富途web開發團隊javascript

夜已深,愚人節有沒有對中意的女生表白啊。哈哈。。。css

小編在這裏先祝福你們。html

這個週末,原本想把最近尚未整理的幾篇文章這裏好發給你們的。無奈小編週末有點暈,還沒整理好。多是週六晚上烤串吃多了。前端

木屋燒烤

最近有一篇是關於module(模塊化) & babel的文章不過還沒整理出來。那先來一篇node.js熱熱身。這篇文章裏面的不少東西,如今富途前端仍是在用,對後面你們去理解富途前端因該會有幫助。vue

有個好消息是,富途的前端框架已經慢慢的被vue替代了(原來使用的是JQ , angular.js)。接入層node.js也在推動中,對外的服務指日可待。相信喜歡vue.js ,node.js的小夥伴已經蠢蠢欲動了。java

沒錯,小編就是富途node.js服務推進者之一。node

在富途作前端,不只須要寫前端,還須要寫node.js服務的,這個你們不用想太多。react

那仍是迴歸正題,做爲第一個吃螃蟹的小編,是如何在工做中學習運用node.js的吧。webpack

注意:如今富途的前端框架已經往vue.js遷移。但不會影響你們理解閱讀。nginx

原文章來源於富途WEB博客: 原文連接

正文

一次偶然的機會讓我有幸跨越瀏覽器的鴻溝來真真切切的體驗一次Node.js。

首先,我想說:「很榮幸在經歷了2個月的努力,第一個Node.js項目落地了」。整個項目作下來,仍是算比較順暢的。

事情很簡單:Node.js作的是接入層。

事出有因

前端的技術革新是突飛猛進的,前端工程化已經離不開Node.js。如今大多數的項目使用的是先後端分離的架構,後端提供接口前端經過接口數據進行數據渲染。可是如今前端的代碼邏輯愈來愈複雜,場景也愈來愈多。這套架構是否適合全部的應用場景值得考慮了。大前端的出現,就是一種嘗試吧。試圖經過Node.js接入來應對各類應用場景。

架構圖

無論是我的仍是團隊,技術革新是必須的。如今咱們團隊面臨的問題就是如此,因此必須有人邁出這一步。而我也很幸運的成爲第一個吃螃蟹的人。

始做俑者

無論什麼技術,無論怎樣的優秀,它的運用與否都是要通過慎重考慮的。但,總不能都不用吧。那怎麼辦呢。找項目試點唄,線上項目運行的好好的確定不能重構,並且人力緊張啊。只能找新項目了。剛巧,公司須要作新的項目,本覺得按老路子先後分離作。可忽然有一天...

組長說:「團隊不是要進行技術選型嗎?看這個項目使用Node.js作接入層可不可行?「。

通過慎重考慮,我回答說:「能夠沒問題。」。(管他3721,應了再說。😄)

借我老大的一句話:「技術這東西不落地,說了也白說」。

背景:其實團隊對Node.js一直都保持着高度的關注,包括我。以前我一直都有在對Node.js的源碼進行解讀和研究。基礎架構組也一直在進行Node.js技術框架進行調研,但願打造一套適用於團隊開發的集成項目框架。

因此我相信:機會老是會照顧有準備的人的。

就這樣個人Node.js之旅就開始了。

萬事開頭難

雖然我平時可能每天都會用Node.js跑命令,寫各類npm包,甚至還寫過一些本身的項目。可是要真正的用Node.js來真正開發項目仍是有壓力的。由於這種項目技術架構下要求我操心的東西變多了。平時的時候可能我只要寫一些前端邏輯代碼,作作前端工程化。可是這種架構下,要求我必須去學習和應用我不熟悉的東西。

我大體列了一些大的方向:

  • 1.Node.js接入層的整體架構是怎樣的?
  • 2.前端技術用什麼?
  • 3.前端工程化如何作?
  • 4.項目如何根據不一樣的環境(常有的環境:開發,測試,正式)運行?
  • 5.前端自動化怎麼搞?
  • 6.單元測試?
  • 7.編碼風格?
  • 8.Node.js如何和服務端對接?
  • 9.日誌,上報,登陸服務接入,權限校驗等等我應該怎麼作?
  • 10.項目如何發佈上線?
  • 11.上線瞭如何保證服務穩定?
  • 12.如何debug問題?

可能還有不少不少須要處理的問題可是這已經能夠看出一下端倪了。瞬間感受我懂的只有冰山一角。代碼碼的再漂亮感受也無力。要求的再也不是單一的編碼能力,而是大局觀,思惟角度的轉變。

但無論怎樣,新建git倉庫開始搞唄。

如何獲得一個合適的項目架構

這個確實是個問題,架構設計的合不合理。會影響到後期編碼是否能夠作到快速開發,還會影響後期的功能迭代和維護。

那麼問題來了,我是預先設計仍是預先編碼?

這裏我選擇了先編碼,而後重構。

背景:由於上文已經說過,基礎架構組已經有一個簡單的Node.js集成框架,它是不完整的,可是它夠簡單。也就是說我在這上面重構出本身的項目架構是徹底沒有問題的。

你可能會以爲仍是要預先設計啊?

說的是側重點不同,側重於編碼實現,將這個項目跑起來,而後經過重構去尋找出合適的項目架構。

對於先編碼仍是設計這個問題我借用重構裏面的是一句話:

「重構改變了預先設計的角色。若是沒有重構,你就必須保證預先作出的設計是正確無誤,這壓力太大了。這意味着若是未來須要對原始設計作任何修改,代價都將很是昂貴。所以你須要把更多的精力放在預先設計上,以免往後的修改。若是選擇重構,問題的重點就轉變了。你任然作預先設計,可是沒必要必定要找出證正確的解決方案,此刻的你只須要獲得一個合理的解決方案就夠了。「 --摘自《重構-改善既有代碼的設計》

把一個簡單的解決方法重構成一個靈活的解決方法有多難?答案是:「至關容易」。 --摘自《重構-改善既有代碼的設計》

實在不明白我推薦你去看看《重構-改善既有代碼的設計》這本書。

因此我將側重點放在了預先編碼上,讓後在整個項目demo跑起來以後再去尋找合適的架構。一個合理的架構體系就是把代碼放到它應該出現的位置上去。代碼是具備流失性的,就比如一個房間歷來不整理的話,就會變的髒亂不堪。重構就是將代碼再次整理將它放回原位。

目錄圖

技術框架選型考慮

技術框架的選擇會影響着項目的整體架構,編碼,產出效益,以及後期人員維護的成本。

首先我想說:「無論前端仍是後端用什麼框架我以爲仍是要站在團隊的角度上去考慮這個問題,畢竟這不是我的的項目。總不能說我不在就沒人能維護這個項目吧」。

Node.js後端

koa2。爲何沒有使用koa或者express等框架,或者爲何團隊不本身開發。

Node.js v8LTS 已經快要來臨。koa已經升級到了koa2版本,沒有必要再用舊的express太老了。koa2在這兩年已經鋒芒畢露,現階段團隊沒有必要花費不少的人力去搞一套本身的框架,能夠轉變思惟在koa2的基礎上作一個集成的適合團隊項目使用的框架。

基於這個基礎架構團隊使用koa2做爲主框架使用在現階段是最合適的。特別是在Node.js v7.6+ 原生支持了asyncawait語法。

前端框架

jQuery的王朝已經漸漸被瓦解。angular.js,react和vue三足鼎立的時代已經到來。再次基於團隊的現狀,選擇了最有優點的angular.js v1.x。

在這裏我並無說其餘框架很差的意思,徹底是基於團隊現狀的考慮,以及當前框架是否能夠幫助我高效的完成開發的一種考慮。假若有一天我以爲angular.js已經不適合現階段項目開發需求,我會責無旁貸的提出個人疑問。

好比:項目須要咱們考慮加速頁面渲染時,要考慮服務器渲染;服務器壓力山大時,考慮先後端分離。同構做爲最合適的編碼方式react和vue都是不錯的選擇。

框架沒有對與錯,只有合不合適。

webpack2 做爲當紅炸子雞,我也是優先考慮的。至於爲何沒有選webpack3嘛。。。

實際上是這樣的,我也有實際的去使用webpack3來作過測試,就是這個項目。個人衡量標準就是壓縮要比如今的要小。最後沒有達到預期效果因此沒有進行合併。

gulp 工做流處理,沒毛病。這裏可能會有的讓人疑惑,爲何使用了webpack2 還要使用gulp?爲何2個都要用?

其實對於這2個組件,它們沒有絕對的對立關係。在這裏它們是相輔相成的。

總的前端框架:angular.js v1.x + webpack2 + gulp。

babel用來編譯前端代碼。

項目使用的主要框架,如圖:

主要框架圖

前端工程化

項目的整體架構和前端技術框架的選型勢必會對前端工程化產生深遠的影響。前端代碼放到哪裏,webpack打包如何作,產出文件放到哪裏。gulp須要作哪些事情,多仍是少,煩不煩瑣。這種種問題都會對你項目的架構作出挑戰。這也就是我爲何先編碼而後經過重構來調整項目架構的緣由之一。假如你預先就把項目的整體架構規定死了,那麼後期你的編碼就會想盡辦法的去套這個項目架構,寫出來的代碼可想而知——必定是不盡人意的。

那麼第一個問題就來了。

本身編寫的anglaur.js部分的源碼放到哪裏

對於這個問題,在使用Node.js開發初期,我就對基礎的架構作出了建議:前端源碼不能放到服務器靜態資源目錄。只有打包後的文件纔會放到靜態資源文件目錄,除非該文件能夠直訪問。

這就意味着,我須要尋找一個文件目錄來放置前端源代碼。最合理的位置就是於服務器目錄平級放置。

webpack

經過webpack的編譯打包,將文件保存到靜態資源目錄。我這裏把因此和代碼相關的打包和編譯任務都交給了webpack,其中還包含公共文件的提取,版本控制,壓縮,以及模版文件注入。

webpack

如何進行版本控制

版本控制用的比較多的就2種:基於文件和基於hash。

基於文件就比如,每次打包的時候都會生成不一樣文件名的文件。有利於在線上跑多個版本的功能。

基於hash就意味着線上這個功能的文件永遠就只有一個,沒法進行全量灰度。

這裏有個問題就是:基於文件的版本控制,難點就在於打包後的.js.css文件名是不可控的,因此,並不能把引入的js或css文件路徑寫死在html模版文件裏面。因此經過webpack打包的時候,我須要指定模版文件是哪個,經過webpack的模版文件注入插件完成js或css文件路徑的引入。

其它方式;經過在webpack打包完成以後,將返回值種的hash參數保存下來。這樣也能夠完成基於文件的版本控制。

gulp的工做流

gulp結合webpack的應用如魚得水,webpack打包任務是gulp任務流裏最重要的一環。考慮到打包編譯,都交給webpack作了。那gulp所要作的就是保證前端各個任務正確的執行。包括什麼時候執行webpack打包,完成打包之後作什麼。

gulp

前端自動化

這裏的自動化可能與你在別的地方所說的自動化可能有分歧。這裏的前端自動化主要指的是在前端代碼如何完成自動化打包編譯。其實項目中能夠進行自動化的流程有不少,我在項目裏接入的是jenkins,主要用來自動完成前端打包編譯,而後經過zip命令對webpack打包編譯後的全部文件進行打包成.zip文件。由於打包後的文件不入庫。

這裏有疑惑是正常的。首先爲何不把webpack打包後生成的文件歸入git版本庫?

道理很簡單,git版本庫裏面的任意一個文件產生變化,就會有下一個版本號產生。webpack每次打包編譯就勢必會產生文件變化,若是把打包文件歸入版本庫就必須提交文件,從而產生版本號。也就是說我本地提交一次代碼到git庫後,jenkins會進行打包,而後打包文件又必須提交回git庫,這樣就至關於每次提交代碼否會產生2次提交記錄(一次我本身的提交,一次jenkins完成自動化打包後的提交。)。因此爲了避免讓jenkins完成打包後向git代碼庫提交文件,所要作的就是把webpack打包後產生的文件都移除版本庫。

但問題沒有這麼簡單,webpack打包不歸入版本庫,發佈的時候,這些webpack打包後產生文件怎麼發佈。這裏解決方案就是經過把全部和webpack打包相關的文件用zip命令打包成一個${commitId}.zip包(commitId 是git每次提交參數的能夠經過bash獲取:commitId=$(git rev-parse HEAD))。這樣發佈的時候就能夠經過commitId找到${commitId}.zip這個壓縮包,而後解壓它到指定位置便可。

爲何有2個打包任務?

第一次是webpack打包,前端代碼須要打包編譯。第二次是文件打包,發佈須要,緣由很就是webpack打包文件不入庫的解決方法。

因此要求團隊中必須會搭建而且有使用過jenkins,這個工具對團隊的幫助是很是大的,預先打包文件並緩存,比在發佈項目的時候再進行打包要好不少。能夠預先發現打包問題及時進行補救,以避免發佈時打包出現問題而影響發佈進度和線上項目的正常運行。

jenkins

git倉庫支持添加hooks。因此能夠在git庫裏添加觸發事件。讓jenkins自動完成打包。

假若有一天,我須要寫單元測試的時候,也能夠試着讓jenkins幫我跑自動化測試了。這算是我回答了單元測試的問題嗎?哈哈哈哈哈哈哈。。。。。。

前端問題基本解決了,如今問題拋到了服務端。

Node.js服務端運行環境配置

寫個項目,要跑起來很簡單,個人項目入口文件是server/index.js。經過執行以下命令就能夠啓動:

node server/index.js
複製代碼

但有時候,環境並無我想的那麼簡單。由於項目須要針對不一樣的環境運行,因此必需對不一樣的運行環境使用不一樣的配置文件。這樣就須要我在啓動Node.js服務的時候,必須攜帶不一樣的參數。因此要求我在編碼的時候儘量的作到環境參數的配置化——牽涉到與執行環境有關的參數儘可能進行配置化。

啓動

Node.js接入層服務的接入,權限的校驗

其實對於一個小白來講,很擔憂的是我如何才能在Node.js裏面往真正的服務器發起request請求。我項目站點的登陸服務鑑權如何去作,以及用戶登陸了,有沒有權限去訪問都是個問題。

http服務的接入

經過http模塊發起requset請求。其實開始的時候我也是一臉茫然的,如何在接入層請求後端服務,可想而知這是以前做爲前端的我歷來沒有考慮過的。如今回想起來就那麼回事。有些事情想着可能很複雜,真正的作起來就好像有種:山重水複疑無路,柳暗花明又一春。的感受。

服務接入

Node.js接入層請求後端服務簡單的代碼實現:

exports.example = async (ctx)=>{
  let options = {
    port: 80,
    hostname: 'www.test.com',
    method:'GET',
    path:'/api/getuser?token=document.cookie.token'
  };
  let getData = function (){
    return new Promise((resolve , reject)=>{
      let request = http.request(options , (socket)=>{
        let data = '';
        console.log('status: ' , socket.statusCode , socket.headers);
        socket.on('data' , (chunk)=>{
          data += chunk;
        });
        socket.on('end' , ()=>{
          console.log('server call back get data: ' , data);
          return resolve(data);
        });
        socket.on('error' , (e)=>{
          return reject(data);
        });
      });
      request.end();
    });
  }
  ctx.body = await getData();
}
複製代碼

這裏我沒有考慮https的方式,由於https是創建在SSL/TLS之上的,也就是說,須要有私鑰和公鑰和CA證書才行。CA證書雖然說能夠本身頒發但仍是得本機自行安裝纔有效。對https本身頒發CA證書感興趣的能夠看看這篇文章:HTTPS自簽發CA證書

後端服務器(PHP/JAVA...)須要作的就是根據請求參數是否合法已經齊全,而後驗證調用者是否有權限使用該功能。這樣的案例比比皆是,好比使用第三方服務。

小到Number校驗

有可能最簡單的參數校驗都不知道如何校驗。這跟javascript語言以及前端的思惟方式有關。我開始的時候也是這樣,感受寫起代碼來怪怪的。

其實這是一個簡單的例子,在前端檢驗一個Number類型的值是否是有效,我通常是經過:

num = typeof num === 'number' && num === num && num !== Infinity ? num : 0;
複製代碼

這種思路和邏輯放在前端徹底是沒有問題的,可是在Node.js接入層這麼寫感受很尷尬。因此要轉變個人思惟方式:

num = Number.isFinite(num) ? num : 0;
複製代碼

小到參數的校驗,我都要認真的考慮。是時候改變本身的思惟方式了,考慮使用JavaScript原生的方式處理會比本身寫好不少。

權限的校驗

我並不但願全部的用戶都能訪問這個項目,即便他已經登陸了也不行。這就是我要解決的問題。

權限

權限管理在這裏就顯得極其重要了。最好的方式就是把權限相關的功能進行服務化。


使命感受纔剛剛開始!!!!!

項目的部署上線

能夠說我對項目部署和運維基本上是沒有經驗。可是有一點就是項目上線後的可用率是必需要保證的。不能由於一點小問題,就讓服務掛掉,而後還要人屁顛屁顛的從新手動重啓吧。也不能說服務器斷電了,重啓後也要手動啓動吧。這一些列的問題都是必須解決的。

pm2

很高效的開發完成了項目後,其實項目的真正使命纔要剛剛開始,如何保證服務在線上穩定的運行,保證高可用率。這就須要藉助其它組件來完成了。使用pm2管理確實是個好的方案。

  1. 首先經過npm install -g pm2進行安裝。

  2. 安裝完成了以後,就能夠在項目中進行pm2相關配置。

案例:

//test.config.js
'use strict';
//pm2配置文件
module.exports = {
    apps:[{
        name : 'test',
        script: './server/index.js',//應用入口
        cwd: './',
        instances : 1,
        watch : ['server'],
        env: {
            'NODE_ENV': 'development',
        },
        env_production: {
            'NODE_ENV': 'production',
        },
        exec_mode : 'cluster',
        source_map_support : true,
        max_memory_restart : '1G',
        //日誌地址
        error_file : '/data/logs/pm2/test_error.log',
        out_file : '/data/logs/pm2/test_access.log',
        listen_timeout : 8000,
        kill_timeout : 2000,
        restart_delay : 10000, //異常狀況
        max_restarts : 10
    }]
};
複製代碼
  1. 而後就能夠經過命令啓動:
pm2 start test.config.js
複製代碼

nginx

Nginx 是俄羅斯人編寫的十分輕量級的 HTTP 服務器,Nginx,它的發音爲「engine X」,是一個高性能的HTTP和反向代理服務器。nginx配置也是必不可少的,80端口就一個,因此我須要nginx進行轉發。

例以下面的案例:

upstream test_upstream {
    server 127.0.0.1:6666;
    keepalive 64;
}
server{
    listen 80;
    server_name www.test.com;
    client_max_body_size 10M;
    
    index index.html index.htm;
    error_log /data/nginx/log/error_www.test.com.log;
    access_log /data/nginx/log/access_www.test.com.log combined;
 
    location / {
        proxy_store off;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Remote-Host $remote_addr;
        proxy_set_header X-Nginx-Proxy true;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_pass http://test_upstream/;
        proxy_read_timeout 60s;
    }
}
複製代碼

項目啓動的端口是本機的6666端口,可是我不可能說訪問www.test.com的時候後面還帶着端口號吧。這個時候就是nginx發揮做用的時候,訪問域名不帶端口默認使用80端口,由nginx作反向代理到我服務6666端口。

這裏有一點post請求時client_max_body_size參數的設定直接會影響data的大小。

日誌,上報,運營維護

項目的健康與否,都會在日誌和上報中體現。我只須要天天看看日誌,看看視圖就能夠對當天項目的運行狀況作一個大體的瞭解。若是沒有這些輔助的功能,兩眼一抹黑,發生啥事都不知道。

編碼風格

編碼風格方面遵循eslint的語法標準。使用了最新的async/awaitimport語法。

編碼

debug代碼

Node.js已經支持在chrome中直接調試Node.js代碼,只要在啓動項目的時候添加--inspact參數。

node --inspect server/index.js
複製代碼

debug

複製上面紅框的url連接到chrome裏面打開,而後點擊start後,再訪問頁面,須要暫停的時候能夠點擊stop,進行代碼分析。

總結

做爲一個初學者,我只能說Node.js在作接入層上,確實是能夠作到如魚得水,關鍵點就是契機。拋開Node.js接入層,前端的工程化是徹底能夠作的。可是服務器同構渲染是沒有辦法作到的,除非與後端同窗配合;使用Node.js接入層,那麼前端在處理一些棘手的問題時就會遊刃有餘,並且後端服務會獲得更深一層的保護,不至於說後端服務直面攻擊,由於多了一層Node.js接入層在前面。

相關文章
相關標籤/搜索