CDN與存儲(chǔ)之Node.js后端開(kāi)發(fā)

一芒率、CDN與存儲(chǔ)

1攒发、什么是CDN

1.1 CDN(Content Delivery Network)驳癌,即 內(nèi)容分發(fā)網(wǎng)絡(luò)娄琉。

CDN的全稱是Content Delivery Network融击,即內(nèi)容分發(fā)網(wǎng)絡(luò)筑公。CDN是構(gòu)建在現(xiàn)有網(wǎng)絡(luò)基礎(chǔ)之上的智能虛擬網(wǎng)絡(luò),依靠部署在各地的邊緣服務(wù)器尊浪,通過(guò)中心平臺(tái)的負(fù)載均衡匣屡、內(nèi)容分發(fā)、調(diào)度等功能模塊拇涤,使用戶就近獲取所需內(nèi)容捣作,降低網(wǎng)絡(luò)擁塞,提高用戶訪問(wèn)響應(yīng)速度和命中率鹅士。CDN的關(guān)鍵技術(shù)主要有內(nèi)容存儲(chǔ)和分發(fā)技術(shù)券躁。

1.2 簡(jiǎn)介

CDN(Content Delivery Network)是指內(nèi)容分發(fā)網(wǎng)絡(luò),也稱為內(nèi)容傳送網(wǎng)絡(luò)掉盅,這個(gè)概念始于1996年也拜,是美國(guó)麻省理工學(xué)院的一個(gè)研究小組為改善互聯(lián)網(wǎng)的服務(wù)質(zhì)量而提出的。為了能在傳統(tǒng)IP網(wǎng)上發(fā)布豐富的寬帶媒體內(nèi)容趾痘,他們提出在現(xiàn)有互聯(lián)網(wǎng)基礎(chǔ)上建立一個(gè)內(nèi)容分發(fā)平臺(tái)專門為網(wǎng)站提供服務(wù)慢哈,并于1999年成立了專門的CDN服務(wù)公司,為Yahoo提供專業(yè)服務(wù)永票。由于CDN是為加快網(wǎng)絡(luò)訪問(wèn)速度而被優(yōu)化的網(wǎng)絡(luò)覆蓋層卵贱,因此被形象地稱為“網(wǎng)絡(luò)加速器”。

CDN網(wǎng)絡(luò)的誕生大大地改善了互聯(lián)網(wǎng)的服務(wù)質(zhì)量侣集,因此傳統(tǒng)的大型網(wǎng)絡(luò)運(yùn)營(yíng)商紛紛開(kāi)始建設(shè)自己的CDN網(wǎng)絡(luò)键俱,如AT&T、德國(guó)電信肚吏、中國(guó)電信等方妖。隨著市場(chǎng)需求的不斷增加,甚至出現(xiàn)了純粹的CDN網(wǎng)絡(luò)運(yùn)營(yíng)商罚攀,美國(guó)的Akamai就是其中最大的一個(gè),擁有分布在世界各地的1000多個(gè)節(jié)點(diǎn)雌澄。我國(guó)第一家純粹的CDN網(wǎng)絡(luò)服務(wù)公司是北京藍(lán)汛公司斋泄,已從2000年開(kāi)始建立了一個(gè)專門的CDN服務(wù)網(wǎng)絡(luò)一ChinaCache。目前CDN網(wǎng)絡(luò)已經(jīng)突破50個(gè)節(jié)點(diǎn)镐牺,覆蓋中國(guó)六大骨干網(wǎng)絡(luò)一中國(guó)電信炫掐、中國(guó)網(wǎng)通、中國(guó)移動(dòng)睬涧、中國(guó)聯(lián)通募胃、中國(guó)鐵通網(wǎng)絡(luò)以及中國(guó)教育網(wǎng)旗唁,帶寬資源儲(chǔ)備超過(guò)35G,服務(wù)的客戶數(shù)量達(dá)到300多家痹束。

2检疫、CDN的基本工作過(guò)程

2.1 傳統(tǒng)網(wǎng)站的請(qǐng)求響應(yīng)過(guò)程,一般經(jīng)歷以下步驟:

  • 用戶在自己的瀏覽器中輸入要訪問(wèn)的網(wǎng)站域名祷嘶。
  • 瀏覽器向本地DNS服務(wù)器請(qǐng)求對(duì)該域名的解析屎媳。
  • 本地DNS服務(wù)器中如果緩存有這個(gè)域名的解析結(jié)果,則直接響應(yīng)用戶的解析請(qǐng)求论巍。
  • 本地DNS服務(wù)器中如果沒(méi)有關(guān)于這個(gè)域名的解析結(jié)果的緩存烛谊,則以迭代方式向整個(gè)DNS系統(tǒng)請(qǐng)求解析,獲得應(yīng)答后將結(jié)果反饋給瀏覽器嘉汰。
  • 瀏覽器得到域名解析結(jié)果丹禀,就是該域名相應(yīng)的服務(wù)設(shè)備的IP地址 。
  • 瀏覽器獲取IP地址之后鞋怀,經(jīng)過(guò)標(biāo)準(zhǔn)的TCP握手流程双泪,建立TCP連接。
  • 瀏覽器向服務(wù)器發(fā)起HTTP請(qǐng)求接箫。
  • 服務(wù)器將用戶請(qǐng)求內(nèi)容傳送給瀏覽器攒读。
  • 經(jīng)過(guò)標(biāo)準(zhǔn)的TCP揮手流程,斷開(kāi)TCP連接辛友。

2.2 CDN用戶訪問(wèn)網(wǎng)站一般經(jīng)歷以下步驟:

  • 當(dāng)用戶點(diǎn)擊網(wǎng)站頁(yè)面上的內(nèi)容URL薄扁,先經(jīng)過(guò)本地DNS系統(tǒng)解析,如果本地DNS服務(wù)器沒(méi)有相應(yīng)域名的緩存废累,則本地DNS系統(tǒng)會(huì)將域名的解析權(quán)交給CNAME指向的CDN專用DNS服務(wù)器邓梅。
  • CDN的DNS服務(wù)器將CDN的全局負(fù)載均衡設(shè)備IP地址返回給用戶。
  • 用戶向CDN的全局負(fù)載均衡設(shè)備發(fā)起URL訪問(wèn)請(qǐng)求邑滨。
  • CDN全局負(fù)載均衡設(shè)備根據(jù)用戶IP地址日缨,以及用戶請(qǐng)求的URL,選擇一臺(tái)用戶所屬區(qū)域的區(qū)域負(fù)載均衡設(shè)備掖看,并將請(qǐng)求轉(zhuǎn)發(fā)到此設(shè)備上匣距。
  • 基于以下這些條件的綜合分析之后,區(qū)域負(fù)載均衡設(shè)備會(huì)選擇一個(gè)最優(yōu)的緩存服務(wù)器節(jié)點(diǎn)哎壳,并從緩存服務(wù)器節(jié)點(diǎn)處得到緩存服務(wù)器的IP地址毅待,最終將得到的IP地址返回給全局負(fù)載均衡設(shè)備:
  • 根據(jù)用戶IP地址,判斷哪一個(gè)邊緣節(jié)點(diǎn)距用戶最近归榕;
  • 根據(jù)用戶所請(qǐng)求的URL中攜帶的內(nèi)容名稱尸红,判斷哪一個(gè)邊緣節(jié)點(diǎn)上有用戶所需內(nèi)容;
  • 查詢各個(gè)邊緣節(jié)點(diǎn)當(dāng)前的負(fù)載情況,判斷哪一個(gè)邊緣節(jié)點(diǎn)尚有服務(wù)能力外里。
  • 全局負(fù)載均衡設(shè)備把服務(wù)器的IP地址返回給用戶怎爵。
  • 用戶向緩存服務(wù)器發(fā)起請(qǐng)求软瞎,緩存服務(wù)器響應(yīng)用戶請(qǐng)求吧兔,將用戶所需內(nèi)容傳送到用戶終端。如果這臺(tái)緩存服務(wù)器上并沒(méi)有用戶想要的內(nèi)容媳友,而區(qū)域均衡設(shè)備依然將它分配給了用戶风科,那么這臺(tái)服務(wù)器就要向它的上一級(jí)緩存服務(wù)器請(qǐng)求內(nèi)容撒轮,直至追溯到網(wǎng)站的源服務(wù)器將內(nèi)容拉到本地。

[圖片上傳失敗...(image-4ce25-1648865490771)]

圖:華為云全站加速示意圖

CDN全局負(fù)載均衡設(shè)備與CDN區(qū)域負(fù)載均衡設(shè)備根據(jù)用戶IP地址贼穆,將域名解析成相應(yīng)節(jié)點(diǎn)中緩存服務(wù)器的IP地址题山,實(shí)現(xiàn)用戶就近訪問(wèn),從而提高服務(wù)端響應(yīng)內(nèi)容的速度故痊。

3顶瞳、CDN的組成

  • CDN網(wǎng)絡(luò)中包含的功能實(shí)體包括內(nèi)容緩存設(shè)備、內(nèi)容交換機(jī)愕秫、內(nèi)容路由器慨菱、CDN內(nèi)容管理系統(tǒng)等組成。

[圖片上傳失敗...(image-fe2732-1648865490771)]

  • 內(nèi)容緩存為CDN網(wǎng)絡(luò)節(jié)點(diǎn)戴甩,位于用戶接入點(diǎn)符喝,是面向最終用戶的內(nèi)容提供設(shè)備,可緩存靜態(tài)Web內(nèi)容和流媒體內(nèi)容甜孤,實(shí)現(xiàn)內(nèi)容的邊緣傳播和存儲(chǔ)协饲,以便用戶的就近訪問(wèn)。

  • 內(nèi)容交換機(jī)處于用戶接入集中點(diǎn)缴川,可以均衡單點(diǎn)多個(gè)內(nèi)容緩存設(shè)備的負(fù)載茉稠,并對(duì)內(nèi)容進(jìn)行緩存負(fù)載平衡及訪問(wèn)控制。

  • 內(nèi)容路由器負(fù)責(zé)將用戶的請(qǐng)求調(diào)度到適當(dāng)?shù)脑O(shè)備上把夸。內(nèi)容路由通常通過(guò)負(fù)載均衡系統(tǒng)來(lái)實(shí)現(xiàn)而线,動(dòng)態(tài)均衡各個(gè)內(nèi)容緩存站點(diǎn)的載荷分配,為用戶的請(qǐng)求選擇最佳的訪問(wèn)站點(diǎn)恋日,同時(shí)提高網(wǎng)站的可用性膀篮。內(nèi)容路由器可根據(jù)多種因素制定路由,包括站點(diǎn)與用戶的臨近度岂膳、內(nèi)容的可用性各拷、網(wǎng)絡(luò)負(fù)載、設(shè)備狀況等闷营。負(fù)載均衡系統(tǒng)是整個(gè)CDN的核心。負(fù)載均衡的準(zhǔn)確性和效率直接決定了整個(gè)CDN的效率和性能。

  • 內(nèi)容管理系統(tǒng)負(fù)責(zé)整個(gè)CDN的管理傻盟,是可選部件速蕊,作用是進(jìn)行內(nèi)容管理,如內(nèi)容的注入和發(fā)布娘赴、內(nèi)容的分發(fā)规哲、內(nèi)容的審核、內(nèi)容的服務(wù)等诽表。

3.1 中心節(jié)點(diǎn)

中心節(jié)點(diǎn)包括CDN網(wǎng)管中心和全局負(fù)載均衡DNS重定向解析系統(tǒng)唉锌,負(fù)責(zé)整個(gè)CDN網(wǎng)絡(luò)的分發(fā)及管理。

3.2 邊緣節(jié)點(diǎn)

  • CDN邊緣節(jié)點(diǎn)主要指異地分發(fā)節(jié)點(diǎn)竿奏,由負(fù)載均衡設(shè)備候址、高速緩存服務(wù)器兩部分組成欢际。

負(fù)載均衡設(shè)備負(fù)責(zé)每個(gè)節(jié)點(diǎn)中各個(gè)Cache的負(fù)載均衡,保證節(jié)點(diǎn)的工作效率偏竟;同時(shí)還負(fù)責(zé)收集節(jié)點(diǎn)與周圍環(huán)境的信息,保持與全局負(fù)載均衡DNS的通信,實(shí)現(xiàn)整個(gè)系統(tǒng)的負(fù)載均衡胡控。

高速緩存服務(wù)器(Cache)負(fù)責(zé)存儲(chǔ)客戶網(wǎng)站的大量信息哼转,就像一個(gè)靠近用戶的網(wǎng)站服務(wù)器一樣響應(yīng)本地用戶的訪問(wèn)請(qǐng)求懂鸵。通過(guò)全局負(fù)載均衡DNS的控制销部,用戶的請(qǐng)求被透明地指向離他最近的節(jié)點(diǎn),節(jié)點(diǎn)中Cache服務(wù)器就像網(wǎng)站的原始服務(wù)器一樣擂涛,響應(yīng)終端用戶的請(qǐng)求读串。因其距離用戶更近,故其響應(yīng)時(shí)間才更快歼指。

中心節(jié)點(diǎn)就像倉(cāng)配網(wǎng)絡(luò)中負(fù)責(zé)貨物調(diào)配的總倉(cāng)爹土,而邊緣節(jié)點(diǎn)就是負(fù)責(zé)存儲(chǔ)貨物的各個(gè)城市的本地倉(cāng)庫(kù)。

目前踩身,主要由很多提供CDN服務(wù)的云廠商在各地部署了很多個(gè)CDN節(jié)點(diǎn)胀茵,拿阿里云舉例,我們可以在阿里云的官網(wǎng)上了解到:阿里云在全球擁有2500+節(jié)點(diǎn)挟阻。中國(guó)大陸擁有2000+節(jié)點(diǎn)琼娘,覆蓋34個(gè)省級(jí)區(qū)域峭弟,大量節(jié)點(diǎn)位于省會(huì)等一線城市。海外和港澳臺(tái)擁有500+節(jié)點(diǎn)脱拼,覆蓋70多個(gè)國(guó)家和地區(qū)瞒瘸。

[圖片上傳失敗...(image-44e92d-1648865490771)]

圖:阿里云在中國(guó)大陸的CDN節(jié)點(diǎn)的分布情況

4、功能

  • 節(jié)省骨干網(wǎng)帶寬熄浓,減少帶寬需求量情臭;

  • 提供服務(wù)器端加速,解決由于用戶訪問(wèn)量大造成的服務(wù)器過(guò)載問(wèn)題赌蔑;

  • 服務(wù)商能使用Web Cache技術(shù)在本地緩存用戶訪問(wèn)過(guò)的Web頁(yè)面和對(duì)象俯在,實(shí)現(xiàn)相同對(duì)象的訪問(wèn)無(wú)須占用主干的出口帶寬,并提高用戶訪問(wèn)因特網(wǎng)頁(yè)面的相應(yīng)時(shí)間的需求娃惯;

  • 能克服網(wǎng)站分布不均的問(wèn)題跷乐,并且能降低網(wǎng)站自身建設(shè)和維護(hù)成本;

  • 降低“通信風(fēng)暴”的影響趾浅,提高網(wǎng)絡(luò)訪問(wèn)的穩(wěn)定性愕提。

5、基本原理

CDN的基本原理是廣泛采用各種緩存服務(wù)器皿哨,將這些緩存服務(wù)器分布到用戶訪問(wèn)相對(duì)集中的地區(qū)或網(wǎng)絡(luò)中浅侨,在用戶訪問(wèn)網(wǎng)站時(shí),利用全局負(fù)載技術(shù)將用戶的訪問(wèn)指向距離最近的工作正常的緩存服務(wù)器上往史,由緩存服務(wù)器直接響應(yīng)用戶請(qǐng)求仗颈。

CDN的基本思路是盡可能避開(kāi)互聯(lián)網(wǎng)上有可能影響數(shù)據(jù)傳輸速度和穩(wěn)定性的瓶頸和環(huán)節(jié),使內(nèi)容傳輸?shù)母熳道⒏€(wěn)定挨决。通過(guò)在網(wǎng)絡(luò)各處放置節(jié)點(diǎn)服務(wù)器所構(gòu)成的在現(xiàn)有的互聯(lián)網(wǎng)基礎(chǔ)之上的一層智能虛擬網(wǎng)絡(luò),CDN系統(tǒng)能夠?qū)崟r(shí)地根據(jù)網(wǎng)絡(luò)流量和各節(jié)點(diǎn)的連接订歪、負(fù)載狀況以及到用戶的距離和響應(yīng)時(shí)間等綜合信息將用戶的請(qǐng)求重新導(dǎo)向離用戶最近的服務(wù)節(jié)點(diǎn)上脖祈。其目的是使用戶可就近取得所需內(nèi)容,解決 Internet網(wǎng)絡(luò)擁擠的狀況刷晋,提高用戶訪問(wèn)網(wǎng)站的響應(yīng)速度

6盖高、關(guān)鍵技術(shù)

6.1 內(nèi)容發(fā)布

它借助于建立索引、緩存眼虱、流分裂喻奥、組播(Multicast)等技術(shù),將內(nèi)容發(fā)布或投遞到距離用戶最近的遠(yuǎn)程服務(wù)點(diǎn)(POP)處捏悬。

6.2 內(nèi)容存儲(chǔ)

對(duì)于CDN系統(tǒng)而言撞蚕,需要考慮兩個(gè)方面的內(nèi)容存儲(chǔ)問(wèn)題。一個(gè)是內(nèi)容源的存儲(chǔ)过牙,一個(gè)是內(nèi)容在 Cache節(jié)點(diǎn)中的存儲(chǔ)甥厦。

6.3 內(nèi)容路由

它是整體性的網(wǎng)絡(luò)負(fù)載均衡技術(shù)纺铭,通過(guò)內(nèi)容路由器中的重定向(DNS)機(jī)制,在多個(gè)遠(yuǎn)程POP上均衡用戶的請(qǐng)求刀疙,以使用戶請(qǐng)求得到最近內(nèi)容源的響應(yīng)舶赔。

6.4 內(nèi)容管理

它通過(guò)內(nèi)部和外部監(jiān)控系統(tǒng),獲取網(wǎng)絡(luò)部件的狀況信息谦秧,測(cè)量?jī)?nèi)容發(fā)布的端到端性能(如包丟失竟纳、延時(shí)、平均帶寬油够、啟動(dòng)時(shí)間蚁袭、幀速率等),保證網(wǎng)絡(luò)處于最佳的運(yùn)行狀態(tài)石咬。

7、服務(wù)模式

內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)是一種新型網(wǎng)絡(luò)構(gòu)建方式卖哎,它是為能在傳統(tǒng)的IP網(wǎng)發(fā)布寬帶豐富媒體而特別優(yōu)化的網(wǎng)絡(luò)覆蓋層鬼悠;而從廣義的角度,CDN代表了一種基于質(zhì)量與秩序的網(wǎng)絡(luò)服務(wù)模式亏娜。

簡(jiǎn)單地說(shuō)焕窝,內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)是一個(gè)經(jīng)策略性部署的整體系統(tǒng),包括分布式存儲(chǔ)维贺、負(fù)載均衡它掂、網(wǎng)絡(luò)請(qǐng)求的重定向和內(nèi)容管理4個(gè)要件,而內(nèi)容管理和全局的網(wǎng)絡(luò)流量管理(Traffic Management)是CDN的核心所在溯泣。通過(guò)用戶就近性和服務(wù)器負(fù)載的判斷虐秋,CDN確保內(nèi)容以一種極為高效的方式為用戶的請(qǐng)求提供服務(wù)。

總的來(lái)說(shuō)垃沦,內(nèi)容服務(wù)基于緩存服務(wù)器客给,也稱作代理緩存(Surrogate),它位于網(wǎng)絡(luò)的邊緣肢簿,距用戶僅有"一跳"(Single Hop)之遙靶剑。同時(shí),代理緩存是內(nèi)容提供商源服務(wù)器(通常位于CDN服務(wù)提供商的數(shù)據(jù)中心)的一個(gè)透明鏡像池充。這樣的架構(gòu)使得CDN服務(wù)提供商能夠代表他們客戶桩引,即內(nèi)容供應(yīng)商,向最終用戶提供盡可能好的體驗(yàn)收夸,而這些用戶是不能容忍請(qǐng)求響應(yīng)時(shí)間有任何延遲的坑匠。

8、主要特點(diǎn)

  • 本地Cache加速:提高了企業(yè)站點(diǎn)(尤其含有大量圖片和靜態(tài)頁(yè)面站點(diǎn))的訪問(wèn)速度咱圆,并大大提高以上性質(zhì)站點(diǎn)的穩(wěn)定性笛辟。

  • 鏡像服務(wù):消除了不同運(yùn)營(yíng)商之間互聯(lián)的瓶頸造成的影響功氨,實(shí)現(xiàn)了跨運(yùn)營(yíng)商的網(wǎng)絡(luò)加速,保證不同網(wǎng)絡(luò)中的用戶都能得到良好的訪問(wèn)質(zhì)量手幢。

  • 遠(yuǎn)程加速遠(yuǎn)程訪問(wèn)用戶根據(jù)DNS負(fù)載均衡技術(shù)智能自動(dòng)選擇Cache服務(wù)器捷凄,選擇最快的Cache服務(wù)器,加快遠(yuǎn)程訪問(wèn)的速度围来。

  • 帶寬優(yōu)化:自動(dòng)生成服務(wù)器的遠(yuǎn)程Mirror(鏡像)cache服務(wù)器跺涤,遠(yuǎn)程用戶訪問(wèn)時(shí)從cache服務(wù)器上讀取數(shù)據(jù),減少遠(yuǎn)程訪問(wèn)的帶寬监透、分擔(dān)網(wǎng)絡(luò)流量桶错、減輕原站點(diǎn)WEB服務(wù)器負(fù)載等功能。

  • 集群抗攻擊:廣泛分布的CDN節(jié)點(diǎn)加上節(jié)點(diǎn)之間的智能冗余機(jī)制胀蛮,可以有效地預(yù)防黑客入侵以及降低各種D.D.o.S攻擊對(duì)網(wǎng)站的影響院刁,同時(shí)保證較好的服務(wù)質(zhì)量 。

二粪狼、PM2

1退腥、PM2是什么

  • 是可以用于生產(chǎn)環(huán)境的Nodejs的進(jìn)程管理工具,并且它內(nèi)置一個(gè)負(fù)載均衡再榄。它不僅可以保證服務(wù)不會(huì)中斷一直在線狡刘,并且提供0秒reload功能,還有其他一系列進(jìn)程管理困鸥、監(jiān)控功能嗅蔬。并且使用起來(lái)非常簡(jiǎn)單。
  • 嗯嗯疾就,最好的用處就是監(jiān)控我們的生產(chǎn)環(huán)境下的node程序運(yùn)行狀態(tài)澜术,讓它給我們?nèi)找岳^日的處于工作狀態(tài)。
  • pm2官方文檔

2虐译、為何使用pm2

2.1 通常開(kāi)發(fā)node服務(wù)端程序一般過(guò)程:

  • 編寫好node程序app.js瘪板,運(yùn)行node app.js;或者寫入script使用npm運(yùn)行;打開(kāi)瀏覽器訪問(wèn)漆诽;
  • 好像需要修改內(nèi)容侮攀,瀏覽器對(duì)修改的內(nèi)容沒(méi)有顯示出來(lái)?->node app.js->再次運(yùn)行厢拭;
  • 瀏覽器忽然訪問(wèn)不到服務(wù)兰英,好像出錯(cuò)啦?重啟下->node app.js->再次運(yùn)行供鸠;
  • 哎呀開(kāi)了好多控制臺(tái)窗口畦贸,一不小心關(guān)閉了,服務(wù)又訪問(wèn)不到了,繼續(xù)打開(kāi)控制臺(tái)->node app.js->再次運(yùn)行薄坏;

2.2 nodemon工具

  • 工具nodemon趋厉;安裝使用nodemon app.js;可以自動(dòng)監(jiān)聽(tīng)文件修改變化自動(dòng)重啟,但是關(guān)閉控制臺(tái)服務(wù)還是會(huì)被摧毀胶坠。

通過(guò)這個(gè)很常用的場(chǎng)景君账,我們了解到要避免這些麻煩一個(gè)服務(wù)器至少需要有:后臺(tái)運(yùn)行和自動(dòng)重啟,這兩個(gè)能力沈善。

2.3 使用pm2可擁有的能力:

  • 日志管理乡数;兩種日志,pm2系統(tǒng)日志與管理的進(jìn)程日志闻牡,默認(rèn)會(huì)把進(jìn)程的控制臺(tái)輸出記錄到日志中净赴;
  • 負(fù)載均衡:PM2可以通過(guò)創(chuàng)建共享同一服務(wù)器端口的多個(gè)子進(jìn)程來(lái)擴(kuò)展您的應(yīng)用程序。這樣做還允許以零秒停機(jī)時(shí)間重新啟動(dòng)應(yīng)用程序罩润。
  • 終端監(jiān)控:可以在終端中監(jiān)控應(yīng)用程序并檢查應(yīng)用程序運(yùn)行狀況(CPU使用率玖翅,使用的內(nèi)存,請(qǐng)求/分鐘等)割以。
  • SSH部署:自動(dòng)部署烧栋,避免逐個(gè)在所有服務(wù)器中進(jìn)行ssh。
  • 靜態(tài)服務(wù):支持靜態(tài)服務(wù)器功能
  • 支持開(kāi)發(fā)調(diào)試模式拳球,非后臺(tái)運(yùn)行,pm2-dev start <appName>珍特;

3祝峻、PM2的主要特性:

  • 內(nèi)建負(fù)載均衡(使用Node cluster 集群模塊)
  • 后臺(tái)運(yùn)行
  • 0秒停機(jī)重載
  • 具有Ubuntu和CentOS 的啟動(dòng)腳本
  • 內(nèi)存的使用 過(guò)多了 CPU調(diào)度太頻繁 會(huì)幫助你重啟
  • 停止不穩(wěn)定的進(jìn)程(避免無(wú)限循環(huán))
  • 控制臺(tái)檢測(cè)
  • 提供 HTTP API
  • 遠(yuǎn)程控制和實(shí)時(shí)的接口API ( Nodejs 模塊,允許和PM2進(jìn)程管理器交互 )
  • 查看restart 個(gè)數(shù) 就能知道代碼是否有問(wèn)題,但是提前要走壓測(cè)扎筒,wrk

4莱找、常用命令

1. pm2需要全局安裝
npm install -g pm2
2. 進(jìn)入項(xiàng)目根目錄
2.1 啟動(dòng)進(jìn)程/應(yīng)用 pm2 start bin/www 或 pm2 start app.js

2.2 重命名進(jìn)程/應(yīng)用 pm2 start app.js --name wb123

2.3 添加進(jìn)程/應(yīng)用 watch pm2 start bin/www --watch

2.4 結(jié)束進(jìn)程/應(yīng)用 pm2 stop www

2.5 結(jié)束所有進(jìn)程/應(yīng)用 pm2 stop all

2.6 刪除進(jìn)程/應(yīng)用 pm2 delete www

2.7 刪除所有進(jìn)程/應(yīng)用 pm2 delete all

2.8 列出所有進(jìn)程/應(yīng)用 pm2 list

2.9 查看某個(gè)進(jìn)程/應(yīng)用具體情況 pm2 describe www

2.10 查看進(jìn)程/應(yīng)用的資源消耗情況 pm2 monit

2.11 查看pm2的日志 pm2 logs

2.12 若要查看某個(gè)進(jìn)程/應(yīng)用的日志,使用 pm2 logs www

2.13 重新啟動(dòng)進(jìn)程/應(yīng)用 pm2 restart www

2.14 重新啟動(dòng)所有進(jìn)程/應(yīng)用 pm2 restart all

5、PM2.json

{
    "apps": [
    {
        "name": "worker",
        "script": "./app.js",
        "out_fil": "log/node-app.stdout.log",
        "watch": true,
        "instances" : "max",
        "exec_mode" : "cluster"
    }]
}

5.1 說(shuō)明:

  • name:應(yīng)用程序名稱
  • script:應(yīng)用程序的腳本路徑
  • out_file:自定義應(yīng)用程序日志文件
  • watch:監(jiān)聽(tīng)?wèi)?yīng)用目錄的變化嗜桌,一旦發(fā)生變化奥溺,自動(dòng)重啟
  • instances:啟用多少個(gè)實(shí)例,用于負(fù)載均衡
  • exec_mode:應(yīng)用程序啟動(dòng)模式锄列,這里設(shè)置的是cluster_mode(集群)赠制,默認(rèn)是fork

三、Docker

1丈探、微服務(wù)架構(gòu)介紹

  • 通過(guò)將功能分解到各個(gè)離散的服務(wù)中以實(shí)現(xiàn)對(duì)解決方案的解耦层亿。

  • 你可以將其看作是在架構(gòu)層次而非獲取服務(wù)的

1.1 程序開(kāi)發(fā)的角度:

把一個(gè)大型的單個(gè)應(yīng)用程序和服務(wù)拆分為數(shù)個(gè)甚至數(shù)十個(gè)的支持微服務(wù)桦卒,它可擴(kuò)展單個(gè)組件而不是整個(gè)的應(yīng)用程序堆棧,從而滿足服務(wù)等級(jí)協(xié)議匿又。

1.2 定義:

圍繞業(yè)務(wù)領(lǐng)域組件來(lái)創(chuàng)建應(yīng)用方灾,這些應(yīng)用可獨(dú)立地進(jìn)行開(kāi)發(fā)、管理和迭代。在分散的組件中

使用云架構(gòu)和平臺(tái)式部署裕偿、管理和服務(wù)功能洞慎,使產(chǎn)品交付變得更加簡(jiǎn)單。

微服務(wù)(Microservice)這個(gè)概念是2012年出現(xiàn)的嘿棘,作為加快Web和移動(dòng)應(yīng)用程序開(kāi)發(fā)進(jìn)程的一種方

法劲腿,2014年開(kāi)始受到各方的關(guān)注,而2015年蔫巩,可以說(shuō)是微服務(wù)的元年谆棱;

2、傳統(tǒng)開(kāi)發(fā)模式和微服務(wù)的區(qū)別:

2.1 傳統(tǒng)開(kāi)模式

[圖片上傳失敗...(image-b60a5-1648865490771)]

2.2 缺點(diǎn)

  • 效率低:開(kāi)發(fā)都在同一個(gè)項(xiàng)目改代碼圆仔,相互等待垃瞧,沖突不斷

  • 維護(hù)難:代碼功能耦合在一起,新人不知道何從下手

  • 不靈活:構(gòu)建時(shí)間長(zhǎng)坪郭,任何小修改都要重構(gòu)整個(gè)項(xiàng)目个从,耗時(shí)

  • 穩(wěn)定性差:一個(gè)微小的問(wèn)題,都可能導(dǎo)致整個(gè)應(yīng)用掛掉

  • 擴(kuò)展性不夠:無(wú)法滿足高并發(fā)下的業(yè)務(wù)需求

[圖片上傳失敗...(image-d358ed-1648865490771)]

2.3 基于微服務(wù)的開(kāi)發(fā)模式

  • 一些列的獨(dú)立的服務(wù)共同組成系統(tǒng)

  • 一些列的獨(dú)立的服務(wù)共同組成系統(tǒng)

  • 單獨(dú)部署歪沃,跑在自己的進(jìn)程中

  • 每個(gè)服務(wù)為獨(dú)立的業(yè)務(wù)開(kāi)發(fā)

  • 分布式管理

  • 非常強(qiáng)調(diào)隔離性

2.4 Java :一次編譯嗦锐,到處運(yùn)行 JVM Java虛擬機(jī)

  • Docker :真正實(shí)現(xiàn)一次編譯,到處運(yùn)行

  • Docker 是一個(gè)開(kāi)源的應(yīng)用容器引擎沪曙,它基于 Google 公司推出的 Go 語(yǔ)言實(shí)現(xiàn)

  • 讓開(kāi)發(fā)者可以打包他們的應(yīng)用以及依賴包到一個(gè)可移植的鏡像中奕污,然后發(fā)布到任何流行的 Linux或

Windows 機(jī)器上,也可以實(shí)現(xiàn)虛擬化液走。容器是完全使用沙箱機(jī)制碳默,相互之間不會(huì)有任何接口。

3缘眶、虛擬化容器技術(shù)-Docker簡(jiǎn)介

3.1 概述

在計(jì)算機(jī)中嘱根,虛擬化(英語(yǔ):Virtualization)是一種資源管理技術(shù),是將計(jì)算機(jī)的各種實(shí)體資源巷懈,如服務(wù)器该抒、網(wǎng)絡(luò)、內(nèi)存及存儲(chǔ)等顶燕,予以抽象凑保、轉(zhuǎn)換后呈現(xiàn)出來(lái),打破實(shí)體結(jié)構(gòu)間的不可切割的障礙割岛,使用戶可以比原本的組態(tài)更好的方式來(lái)應(yīng)用這些資源愉适。這些資源的新虛擬部份是不受現(xiàn)有資源的架設(shè)方式,地域或物理組態(tài)所限制癣漆。一般所指的虛擬化資源包括計(jì)算能力和資料存儲(chǔ)维咸。

(比如:vmware也是一個(gè)虛擬資源,大家都在使用vmware,大家都是在上面建立一個(gè)虛擬機(jī)癌蓖,其實(shí)這就是一種虛擬化技術(shù)瞬哼,或者半虛擬化技術(shù),大家是不是使用vmware安裝一個(gè)centos的系統(tǒng)或者安裝一個(gè)windows的系統(tǒng)租副。那么你在上面操作其實(shí)和你在實(shí)體機(jī)上操作是不是比較類似坐慰,對(duì)吧,他們之間這種技術(shù)就是虛擬化技術(shù)用僧。

這種虛擬化技術(shù)它有一個(gè)小小的弊端结胀,我們?cè)谖覀兊膙mware上面創(chuàng)建了一個(gè)centos的虛擬機(jī),它在使用的時(shí)候是以我當(dāng)前的操作系統(tǒng)緊密相連的责循,簡(jiǎn)單點(diǎn)說(shuō)糟港,你的操作系統(tǒng)內(nèi)存只有4G,你在創(chuàng)建虛擬機(jī)的時(shí)候你會(huì)分配8個(gè)G嗎院仿?

說(shuō)不能秸抚,說(shuō)明虛擬機(jī)是依賴我們的當(dāng)前的物理系統(tǒng),它只不過(guò)是在之上構(gòu)建了虛擬的系統(tǒng)歹垫,我們就可以使用它剥汤,這種其實(shí)就是一種半虛擬化技術(shù)。)這種虛擬機(jī)技術(shù)排惨,完全依賴底層的宿主機(jī)吭敢,每個(gè)虛擬機(jī)本身都是獨(dú)立的,隔離的暮芭,每一個(gè)都有自己?jiǎn)为?dú)的內(nèi)存省有,資源沒(méi)有辦法達(dá)到共享,資源達(dá)不到最大化的利用和使用谴麦。而docker和它不一樣,它是共享的伸头,大白話就是我的上面有docker容器可以共享我的資源匾效,這個(gè)時(shí)候就會(huì)在宿主機(jī)上或者一臺(tái)主機(jī)上,我可以部署很多個(gè)docker容器恤磷,他們是共享的 docker這種容器技術(shù)面哼,給我們的開(kāi)發(fā)和運(yùn)維去做一個(gè)統(tǒng)一的環(huán)境,是非常非常好的扫步,也是非常高效和快捷的魔策。

大家知道在真實(shí)的開(kāi)發(fā)中,開(kāi)發(fā)人員和與運(yùn)維人員經(jīng)常因?yàn)榄h(huán)境的問(wèn)題出現(xiàn)故障和扯皮河胎,這個(gè)是非常常見(jiàn)的糾紛闯袒,比如你在電腦上的環(huán)境都是你自己安裝部署的,ok你開(kāi)發(fā)完了運(yùn)行沒(méi)有任何問(wèn)題,但是測(cè)試人員在測(cè)試的時(shí)候出問(wèn)題了政敢。為什么呢其徙?因?yàn)樗湍愕沫h(huán)境不一樣,比如你使用的是jdk1.8他使用的是1.7喷户,常常因?yàn)楦鞣N版本的問(wèn)題造成不必要的麻煩唾那,而使用docker這個(gè)容器,首先他的第一件事情就是它能幫助我們統(tǒng)一運(yùn)行環(huán)境褪尝。這樣的話闹获,這樣的話能提高我們的開(kāi)發(fā)效率,因?yàn)榇蠹叶际褂玫氖峭粋€(gè)環(huán)境河哑。所以說(shuō)呀避诽,docker以后在未來(lái)的開(kāi)發(fā)環(huán)境中,用的越來(lái)越多灾馒,但是呢茎用,docker在真正的學(xué)習(xí)過(guò)程中,它是有點(diǎn)偏運(yùn)維方面睬罗。這也就是為什么現(xiàn)在招聘里面有一個(gè)職位叫:運(yùn)維開(kāi)發(fā)工程師

4轨功、Docker和虛擬機(jī)形象比喻

4.1 什么是物理機(jī)

[圖片上傳失敗...(image-8de400-1648865490771)]

4.2 什么是虛擬機(jī)

[圖片上傳失敗...(image-df68df-1648865490771)]

4.3 什么是docker

[圖片上傳失敗...(image-62df22-1648865490771)]

5、虛擬化容器技術(shù)--什么是Docker

5.1 官網(wǎng):https://www.docker.com/

圖例:
[圖片上傳失敗...(image-ec6666-1648865490771)]

一頭鯨魚(yú)通過(guò)身上的集裝箱(Container)來(lái)將不同種類的貨物進(jìn)行隔離容达,而不是通過(guò)生出很多小鯨魚(yú)來(lái)承運(yùn)不同種類的貨物古涧。Docker是一個(gè)開(kāi)源的應(yīng)用容器引擎,基于GO語(yǔ)言并遵從 Apache2.0協(xié)議開(kāi)源花盐。Docker可以讓開(kāi)發(fā)者打包他們的應(yīng)用以及依賴包到一個(gè)輕量級(jí)羡滑、可移植的容器中,然后發(fā)布到任何流行的容器是完成使用沙箱機(jī)制算芯,相互之間不會(huì)有任何接口重疊柒昏,更重要的容器性能開(kāi)銷極低。一個(gè)完整的Docker基本架構(gòu)由如下幾個(gè)部分構(gòu)成:客戶端熙揍,宿主機(jī)职祷,注冊(cè)中心

[圖片上傳失敗...(image-782e77-1648865490771)]

5.2 Docker客戶端

也就是在窗口中執(zhí)行的命令,都是客戶端

5.3 Docker Daemon守護(hù)進(jìn)程

用于去接受client的請(qǐng)求并處理請(qǐng)求

5.4 倉(cāng)庫(kù)

Docker用Registry來(lái)保存用戶構(gòu)建的鏡像届囚。Registry分為公共和私有兩種有梆。Docker公司運(yùn)營(yíng)公共的Registry叫做Docker Hub。遠(yuǎn)程倉(cāng)庫(kù)地址:https://hub.docker.com/

5.5 鏡像

簡(jiǎn)單點(diǎn)說(shuō):鏡像不是單一的文件:而是有多層構(gòu)成意系,我們可以通過(guò) docker history 鏡像名|id 查看鏡像中各層內(nèi)容及大小泥耀,每層都對(duì)應(yīng)著Dockerfifile中的一條指令。Docker鏡像默認(rèn)存在/var/lib/docker/中蛔添。鏡像從何而來(lái):Docker Hub是由docker公司負(fù)責(zé)和維護(hù)的公共注冊(cè)中心痰催,包含大量的鏡像文件兜辞,Docker客戶端工具默認(rèn)從這個(gè)公共鏡像倉(cāng)庫(kù)下載鏡像, 遠(yuǎn)程倉(cāng)庫(kù)地址:https://hub.docker.com/

5.6 容器

容器其實(shí)是在鏡像的最上面加了一層讀寫層陨囊,在運(yùn)行容器里做的任何文件改動(dòng)弦疮,都會(huì)寫到這個(gè)讀寫層。如果容器刪除了蜘醋,最上面的讀寫層也就刪除了胁塞,改動(dòng)也就丟失了。Docker使用存儲(chǔ)驅(qū)動(dòng)管理鏡像每層內(nèi)容及可讀寫層的容器層压语。

6啸罢、虛擬化容器技術(shù)-- 什么鏡像

6.1 遠(yuǎn)程倉(cāng)庫(kù)鏡像地址:https://hub.docker.com/

解釋:鏡像就像你下載了一個(gè)gz或zip壓縮包。只不過(guò)這個(gè)鏡像文件中意見(jiàn)包括了幾個(gè)部分:

  • 微型計(jì)算機(jī)(文件系統(tǒng)胎食,網(wǎng)絡(luò))

  • 當(dāng)前鏡像的文件,比如你下載的tomcat扰才,tomcat的鏡像文件就包括了:微型計(jì)算機(jī) + Tomcat環(huán)境+Jdk環(huán)境 = Tomcat鏡像

7、虛擬化容器技術(shù)-- 什么容器

什么是容器:就是鏡像創(chuàng)建出來(lái)的一個(gè)運(yùn)行的系統(tǒng)厕怜,與其說(shuō)是系統(tǒng)還不如說(shuō)衩匣,容器就是一個(gè)進(jìn)程。就好比你之前下載了tomcat就開(kāi)始進(jìn)行解壓安裝和運(yùn)行粥航。

7.1 當(dāng)下Docker容器化技術(shù)的背景和支撐

[圖片上傳失敗...(image-609bf9-1648865490772)]

[圖片上傳失敗...(image-289ba-1648865490772)]

[圖片上傳失敗...(image-547cea-1648865490772)]

8琅捏、總結(jié)

8.1 什么是Docker

  • 使用最廣泛的開(kāi)源容器.

  • 一種操作系統(tǒng)的虛擬化技術(shù) linux 內(nèi)核

  • 依賴于Linux內(nèi)核特性:NameSpace和Cgroups

  • 一個(gè)簡(jiǎn)單的應(yīng)用程序打包工具

8.2 作用和目的

  • 提供簡(jiǎn)單的應(yīng)用程序打包工具

  • 開(kāi)發(fā)人員和運(yùn)維人員職責(zé)邏輯分離

  • 多環(huán)境保持一致。消除了環(huán)境的差異递雀。

8.3 Docker的應(yīng)用場(chǎng)景

  • 應(yīng)用程序的打包和發(fā)布

  • 應(yīng)用程序隔離

  • 持續(xù)集成

  • 部署微服務(wù)

  • 快速搭建測(cè)試環(huán)境

  • 提供PaaS平臺(tái)級(jí)產(chǎn)品

8.4 容器帶來(lái)的好處有哪些柄延?

  • 秒級(jí)的交付和部署

  • 保證環(huán)境一致性

  • 高效的資源利用

  • 彈性的伸縮

  • 動(dòng)態(tài)調(diào)度遷移成本低

8.5 注意

Docker本身并不是容器,它是創(chuàng)建容器的工具缀程,是應(yīng)用容器引擎 搜吧。

1)想要搞懂Docker,其實(shí)看它的兩句口號(hào)就行 :

  • 第一句杨凑,是“Build, Ship and Run”

[圖片上傳失敗...(image-ee00cb-1648865490772)]

也就是 搭建滤奈、發(fā)送、運(yùn)行 三板斧

  • 第二句口號(hào)就是:“Build?once撩满,Run?anywhere(搭建一次僵刮,到處能用)”。

2)Docker技術(shù)的三大核心概念鹦牛,分別是:

  • 鏡像(Image)

  • 容器(Container)

  • 倉(cāng)庫(kù)(Repository

負(fù)責(zé)對(duì)Docker鏡像進(jìn)行管理的,是Docker Registry服務(wù)(類似倉(cāng)庫(kù)管理員)勇吊。

不是任何人建的任何鏡像都是合法的曼追。萬(wàn)一有人蓋了一個(gè)有問(wèn)題的房子呢?所以汉规,Docker Registry服務(wù)對(duì)鏡像的管理是非常嚴(yán)格的礼殊。最常使用的Registry公開(kāi)服務(wù)驹吮,是官方的Docker Hub,這也是默認(rèn)的Registry晶伦,并擁有大量的高質(zhì)量的官方鏡像碟狞。

9、Dockerfile

9.1 什么是dockerfile

Dockerfile是一個(gè)包含用于組合映像的命令的文本文檔婚陪∽逦郑可以使用在命令行中調(diào)用任何命令。 Docker通過(guò)讀取Dockerfile中的指令自動(dòng)生成映像泌参。

docker build 命令用于從Dockerfile構(gòu)建映像脆淹。可以在docker build命令中使用-f標(biāo)志指向文件系統(tǒng)中任何位置的Dockerfile沽一。

例:

docker build -f /path/to/a/Dockerfile

9.2 Dockerfile的基本結(jié)構(gòu)

Dockerfile 一般分為四部分:基礎(chǔ)鏡像信息盖溺、維護(hù)者信息、鏡像操作指令和容器啟動(dòng)時(shí)執(zhí)行指令铣缠,’#’ 為 Dockerfile 中的注釋烘嘱。

9.3 Dockerfile文件說(shuō)明

Docker以從上到下的順序運(yùn)行Dockerfile的指令。為了指定基本映像蝗蛙,第一條指令必須是FROM蝇庭。一個(gè)聲明以字符開(kāi)頭則被視為注釋〖吖可以在Docker文件中使用RUN遗契,CMDFROM病曾,EXPOSE牍蜂,ENV等指令。
在這里列出了一些常用的指令泰涂。

1. FROM:指定基礎(chǔ)鏡像鲫竞,必須為第一個(gè)命令
格式:
  FROM <image>
  FROM <image>:<tag>
  FROM <image>@<digest>
示例:
  FROM mysql:5.6
注:
  tag或digest是可選的,如果不使用這兩個(gè)值時(shí)逼蒙,會(huì)使用latest版本的基礎(chǔ)鏡像
2.MAINTAINER: 維護(hù)者信息
格式:
    MAINTAINER <name>
示例:
    MAINTAINER Jasper Xu
    MAINTAINER sorex@163.com
    MAINTAINER Jasper Xu <sorex@163.com>
3.RUN:構(gòu)建鏡像時(shí)執(zhí)行的命令
RUN用于在鏡像容器中執(zhí)行命令从绘,其有以下兩種命令執(zhí)行方式:
shell執(zhí)行
格式:
    RUN <command> exec執(zhí)行
格式:
    RUN ["executable", "param1", "param2"]
示例:
    RUN ["executable", "param1", "param2"]
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]
注:
  RUN指令創(chuàng)建的中間鏡像會(huì)被緩存,并會(huì)在下次構(gòu)建中使用是牢。如果不想使用這些緩存鏡像僵井,可以在構(gòu)建時(shí)指定--no-cache參數(shù),如:docker build --no-cache
4.ADD:將本地文件添加到容器中驳棱,tar類型文件會(huì)自動(dòng)解壓(網(wǎng)絡(luò)壓縮資源不會(huì)被解壓)批什,可以訪問(wèn)網(wǎng)絡(luò)資源,類似wget

格式:
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] 用于支持包含空格的路徑
示例:
    ADD hom* /mydir/          # 添加所有以"hom"開(kāi)頭的文件
    ADD hom?.txt /mydir/      # ? 替代一個(gè)單字符,例如:"home.txt"
    ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
    ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/
5.COPY:功能類似ADD社搅,但是是不會(huì)自動(dòng)解壓文件驻债,也不能訪問(wèn)網(wǎng)絡(luò)資源
CMD:構(gòu)建容器后調(diào)用乳规,也就是在容器啟動(dòng)時(shí)才進(jìn)行調(diào)用。

格式:
    CMD ["executable","param1","param2"] (執(zhí)行可執(zhí)行文件合呐,優(yōu)先)
    CMD ["param1","param2"] (設(shè)置了ENTRYPOINT暮的,則直接調(diào)用ENTRYPOINT添加參數(shù))
    CMD command param1 param2 (執(zhí)行shell內(nèi)部命令)
示例:
    CMD echo "This is a test." | wc -
    CMD ["/usr/bin/wc","--help"]
注:
   CMD不同于RUN,CMD用于指定在容器啟動(dòng)時(shí)所要執(zhí)行的命令淌实,而RUN用于指定鏡像構(gòu)建時(shí)所要執(zhí)行的命令冻辩。
6.ENTRYPOINT:配置容器,使其可執(zhí)行化翩伪。配合CMD可省去"application"微猖,只使用參數(shù)。

格式:
    ENTRYPOINT ["executable", "param1", "param2"] (可執(zhí)行文件, 優(yōu)先)
    ENTRYPOINT command param1 param2 (shell內(nèi)部命令)
示例:
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
注:
    ENTRYPOINT與CMD非常類似缘屹,不同的是通過(guò)docker run執(zhí)行的命令不會(huì)覆蓋ENTRYPOINT凛剥,而docker run命令中指定的任何參數(shù),都會(huì)被當(dāng)做參數(shù)再次傳遞給ENTRYPOINT轻姿。Dockerfile中只允許有一個(gè)ENTRYPOINT命令犁珠,多指定時(shí)會(huì)覆蓋前面的設(shè)置,而只執(zhí)行最后的ENTRYPOINT指令
7.LABEL:用于為鏡像添加元數(shù)據(jù)

格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
  LABEL version="1.0" description="這是一個(gè)Web服務(wù)器" by="IT筆錄"
注:
    使用LABEL指定元數(shù)據(jù)時(shí)互亮,一條LABEL指定可以指定一或多條元數(shù)據(jù)犁享,指定多條元數(shù)據(jù)時(shí)不同元數(shù)據(jù)之間通過(guò)空格分隔够滑。推薦將所有的元數(shù)據(jù)通過(guò)一條LABEL指令指定本冲,以免生成過(guò)多的中間鏡像跃赚。
8.ENV:設(shè)置環(huán)境變量
格式:
    ENV <key> <value>  #<key>之后的所有內(nèi)容均會(huì)被視為其<value>的組成部分遵岩,因此,一次只能設(shè)置一個(gè)變量
    ENV <key>=<value> ...  #可以設(shè)置多個(gè)變量徘六,每個(gè)變量為一個(gè)"<key>=<value>"的鍵值對(duì)针炉,如果<key>中包含空格男摧,可以使用\來(lái)進(jìn)行轉(zhuǎn)義洛搀,也可以通過(guò)""來(lái)進(jìn)行標(biāo)示敢茁;另外,反斜線也可以用于續(xù)行
示例:
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat=fluffy
9.EXPOSE:指定于外界交互的端口
格式:
    EXPOSE <port> [<port>...]
示例:
    EXPOSE 80 443
    EXPOSE 8080
    EXPOSE 11211/tcp 11211/udp
注:
  EXPOSE并不會(huì)讓容器的端口訪問(wèn)到主機(jī)留美。要使其可訪問(wèn)彰檬,需要在docker run運(yùn)行容器時(shí)通過(guò)-p來(lái)發(fā)布這些端口,或通過(guò)-P參數(shù)來(lái)發(fā)布EXPOSE導(dǎo)出的所有端口
10.VOLUME:用于指定持久化目錄
格式:
    VOLUME ["/path/to/dir"]
示例:
    VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"
注:
  一個(gè)卷可以存在于一個(gè)或多個(gè)容器的指定目錄谎砾,該目錄可以繞過(guò)聯(lián)合文件系統(tǒng)逢倍,并具有以下功能:

1 卷可以容器間共享和重用
2 容器并不一定要和其它容器共享卷
3 修改卷后會(huì)立即生效
4 對(duì)卷的修改不會(huì)對(duì)鏡像產(chǎn)生影響
5 卷會(huì)一直存在,直到?jīng)]有任何容器在使用它
11.WORKDIR:工作目錄景图,類似于cd命令
格式:
    WORKDIR /path/to/workdir
示例:
    WORKDIR /a  (這時(shí)工作目錄為/a)
    WORKDIR b  (這時(shí)工作目錄為/a/b)
    WORKDIR c  (這時(shí)工作目錄為/a/b/c)
注:
  通過(guò)WORKDIR設(shè)置工作目錄后较雕,Dockerfile中其后的命令RUN、CMD症歇、ENTRYPOINT郎笆、ADD、COPY等命令都會(huì)在該目錄下執(zhí)行忘晤。在使用docker run運(yùn)行容器時(shí)宛蚓,可以通過(guò)-w參數(shù)覆蓋構(gòu)建時(shí)所設(shè)置的工作目錄。
12.USER: 指定運(yùn)行容器時(shí)的用戶名或 UID设塔,后續(xù)的 RUN 也會(huì)使用指定用戶凄吏。使用USER指定用戶時(shí),可以使用用戶名闰蛔、UID或GID痕钢,或是兩者的組合。當(dāng)服務(wù)不需要管理員權(quán)限時(shí)序六,可以通過(guò)該命令指定運(yùn)行用戶任连。并且可以在之前創(chuàng)建所需要的用戶**

 格式:
  USER use
  USER user:group
  USER uid
  USER uid:gid
  USER user:gid
  USER uid:group

 示例:
  USER www

 注:
  使用USER指定用戶后,Dockerfile中其后的命令RUN例诀、CMD随抠、ENTRYPOINT都將使用該用戶。鏡像構(gòu)建完成后繁涂,通過(guò)`docker run`運(yùn)行容器時(shí)拱她,可以通過(guò)-u參數(shù)來(lái)覆蓋所指定的用戶。
13.ARG:用于指定傳遞給構(gòu)建運(yùn)行時(shí)的變量

格式:
    ARG <name>[=<default value>]
示例:
    ARG site
    ARG build_user=www
14.ONBUILD:用于設(shè)置鏡像觸發(fā)器

格式:
  ONBUILD [INSTRUCTION]
示例:
  ONBUILD ADD . /app/src
  ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:
15.

10扔罪、圖片說(shuō)明

[圖片上傳失敗...(image-6f11cb-1648865490772)]

四秉沼、Commander

事實(shí)上,在Node.js或ruby等語(yǔ)言環(huán)境里矿酵,只要在文件頭部添加一行所謂的shebang(提供一個(gè)執(zhí)行環(huán)境)唬复,就可以將代碼轉(zhuǎn)為命令行執(zhí)行。難在命令行選項(xiàng)處理和流程控制坏瘩,所以才有了這類工具的出現(xiàn)盅抚,叫它們命令行框架最合適。

類似Commander的工具有很多倔矾,但多數(shù)以規(guī)范命令行選項(xiàng)為主妄均,對(duì)一些編碼細(xì)節(jié)還要自己實(shí)現(xiàn),比如:何時(shí)退出程序(調(diào)用process.exit(1))哪自。Commander把這一切都簡(jiǎn)化了丰包,小巧靈活、簡(jiǎn)單易用壤巷,有它足夠了邑彪。

1、概念定義

簡(jiǎn)單直接的命令行工具開(kāi)發(fā)組件胧华。

2寄症、概念解釋

  • 這是一個(gè)組件宙彪,說(shuō)明是第三方開(kāi)發(fā)的,其實(shí)就是開(kāi)發(fā)Express的大神tj開(kāi)發(fā)的有巧。ruby語(yǔ)言也有一個(gè)同名的開(kāi)發(fā)組件释漆,同樣是tj的杰作,所以篮迎,雖為組件男图,但足夠權(quán)威,“您值得擁有”甜橱。
  • 命令行工具開(kāi)發(fā)逊笆,Commander的英文解釋是命令,如其名字岂傲,這個(gè)是用來(lái)開(kāi)發(fā)命令行命令的难裆。
  • 簡(jiǎn)單直接,怎么簡(jiǎn)單譬胎?四個(gè)函數(shù)而已差牛。怎么直接?如果您了解“命令行”的話堰乔,就能體會(huì)深刻偏化,它通常包含命令、選項(xiàng)镐侯、幫助和業(yè)務(wù)邏輯四個(gè)部分侦讨,該組件分別提供了對(duì)應(yīng)函數(shù)。

因此苟翻,只要記住該Commander這個(gè)名字和這一句話的概念定義韵卤,基本上已經(jīng)掌握了該組件的全部。下面的用法介紹崇猫,僅僅是幫助您更好的記憶和使用沈条。

3、用法介紹:

這里诅炉,我們也給它概念化蜡歹,叫“命令行開(kāi)發(fā)三步曲”。具體以 gitbook-summary為例涕烧,解釋如下:

3.1 第1步:給工具起名字

這個(gè)名字月而,是工具的名字(其實(shí)也是命令,我叫它主命令)议纯,用來(lái)區(qū)分系統(tǒng)命令父款,限定命令使用的上下文。我通常用工程的名字或操作對(duì)象的名字代替,是個(gè)名詞憨攒,比如:book世杀。而用Commander寫的命令是個(gè)動(dòng)詞(其實(shí)是用.command()方法定義的子命令),比如:generate肝集,最后的形式如下:

$ book generate [--options]

只所以把起名字單獨(dú)提出來(lái)玫坛,主要是在Node.js的世界里,這一步是固定不變的包晰,只要記住就是了。方法是炕吸,在package.json里定義下面的字段:

{
  "bin": {
    "book": "./path/to/your-commander.js"
  }
}

package.json文件是包配置文件伐憾,是全局配置不可逾越之地。很多工具赫模,都是基于它树肃,提供入口程序的。比如:Node.js自己就是請(qǐng)求main字段的(沒(méi)有定義瀑罗,默認(rèn)請(qǐng)求index.js文件)胸嘴,Npm請(qǐng)求scripts字段。這里多了一個(gè)斩祭,Commander請(qǐng)求bin字段劣像。

如果,不使用package.json摧玫,那么定義的就是node命令之下的子命令耳奕,調(diào)用方法是:

$ node ./path/to/your-commander.js generate [--options]

如果連node都不想輸入,那么就要在代碼第一行添加shebang诬像,即:

#!/usr/bin/env node

3.2 第2步:填充四個(gè)函數(shù)

這一步屋群,用于定義命令、選項(xiàng)坏挠、幫助和業(yè)務(wù)邏輯芍躏,完全是Commander概念定義的使用。其實(shí)降狠,第三方組件对竣,也就是起到這種微框架的作用。具體用法喊熟,自然最好是看 官方文檔 了柏肪。這里,需要進(jìn)一步思考的是芥牌,對(duì)于這個(gè)組件而言烦味,這四個(gè)函數(shù),最重要的是什么?

我們想到的通常是業(yè)務(wù)邏輯谬俄,不過(guò)柏靶,請(qǐng)注意,只要是開(kāi)發(fā)溃论,邏輯部分自然只能開(kāi)發(fā)者自己實(shí)現(xiàn)屎蜓,所以,Commander僅僅提供了一個(gè)接口函數(shù)而已钥勋。這里的命令炬转,僅是一個(gè)名稱。幫助是提示算灸,也僅是簡(jiǎn)單的文本信息扼劈。剩下的各種選項(xiàng),可以規(guī)范菲驴,也最為關(guān)鍵荐吵,才是Commander的可愛(ài)之處。

1)命令

使用command函數(shù)定義(子命令)赊瞬,例如

var program = require("commander");

program
    .command("summary <cmd>")
    .alias("sm") //提供一個(gè)別名
    .description("generate a `SUMMARY.md` from a folder") //描述先煎,會(huì)顯示在幫助信息里
...

當(dāng)使用-h選項(xiàng)調(diào)用命令時(shí),上述命令summary|sm會(huì)被顯示在幫助信息里巧涧。這里的aliasdescription僅是錦上添花而已薯蝎。

更復(fù)雜的,例如下面官方的例子谤绳, .command() 包含了描述信息和 .action(callback) 方法調(diào)用良风,就是說(shuō)要用子命令各自對(duì)應(yīng)的執(zhí)行文件,這里就是./pm-install.js闷供,以及 ./pm-search.js 和 ./pm-list.js等烟央。

#!/usr/bin/env node

var program = require('..');

program
  .version('0.0.1')
  .command('install [name]', 'install one or more packages')
  .command('search [query]', 'search with optional query')
  .command('list', 'list packages installed')
  .command('publish', 'publish the package')
  .parse(process.argv);

說(shuō)明:不使用command方法直接定義主命令,個(gè)人建議不要這么做歪脏。中規(guī)中矩地定義每一個(gè)子命令(本文統(tǒng)稱命令)疑俭,只要使用command方法,不帶描述信息婿失,附帶action方法钞艇。如果定義類似git類型的,一連串的命令豪硅,一個(gè)一個(gè)來(lái)哩照,顯然麻煩,就把描述信息放在command里懒浮,去掉action方法飘弧,這時(shí)默認(rèn)請(qǐng)求對(duì)應(yīng)的js文件识藤。

  • 我們的代碼其實(shí)就是主命令
  • 這個(gè)命令的回調(diào)是注冊(cè)一個(gè)action(理解主命令)
program
.action((t) => {
    console.log('top action call',t)
})
.parse(process.argv);

// 輸入:node test
// 輸出:top action call
// 輸入:node test --help
// 觀察

  • 注冊(cè)一個(gè)子命令(理解子命令)
program
.action((t) => {
    console.log('top action call')
})
// 注意,command返回的是子命令對(duì)象次伶,而非主命令
// 所以后續(xù)的.是在配置子命令對(duì)象
.command('cmd1')
.action((t) => {
    console.log('child1 action call')
})
// 從主命令這邊解析痴昧,所以不是.parse。而是program.parse冠王。
// 當(dāng)然赶撰,如果我們也可以從某個(gè)子命令那里開(kāi)始解析,但不推薦
program.parse(process.argv); 

// 說(shuō)明:command語(yǔ)法結(jié)構(gòu):command('命令名 參數(shù)1 參數(shù)2 參數(shù)3','描述')
// 也就是支持多個(gè)參數(shù)柱彻,關(guān)于參數(shù)的使用下面會(huì)有案例分析
// 對(duì)于參數(shù)豪娜,和正常js函數(shù)的參數(shù)一樣理解就行了
// <參數(shù)> : 代表這個(gè)參數(shù)是required的,需要在可選參數(shù)之前
// [參數(shù)] : 代表這個(gè)參數(shù)是可選的
// <參數(shù)...> 或 [參數(shù)...] : 和es6...一樣理解就行了

// 輸入:node test
// 輸出:top action call
// 輸入:node test cmd1
// 輸出:child1 action call
// 說(shuō)明:此時(shí)匹配的是子命令哟楷,而不是主命令侵歇,所以主命令回調(diào)不會(huì)執(zhí)行

  • 注冊(cè)兩個(gè)子命令,及后代命令(理解子命令)
program
.action((t) => {
    console.log('top action call')
})
.command('cmd1')
.action((t) => {
    console.log('child1 action call')
})
.command('cmd11')
.action((t) => {
    console.log('child11 action call')
})

program
.command('cmd2')
.action((t) => {
    console.log('child2 action call')
})
program.parse(process.argv);

// 結(jié)構(gòu)為:
// 主命令
//   --cmd1
//     --cmd11
//   --cmd2

// 輸入:node test
// 輸出:top action call
// 輸入:node test cmd1
// 輸出:child1 action call
// 輸入:node test cmd1 cmd11
// 輸出:child11 action call
// 輸入:node test cmd2
// 輸出:child2 action call

// 觀察
// 輸入:node test --help
// 觀察:commands: cmd1 cmd2
// 輸入:node test cmd1 --help
// 觀察:commands: cmd11

  • 命令 + 參數(shù)(理解參數(shù)的必傳吓蘑、選傳、和...及action回調(diào)參數(shù)的值)
program
.command('cmd1 <arg1> [arg2] [arg3...]')
.action((...t) => {
    console.log('child1 action call',t)
})
program.parse(process.argv); 

// 說(shuō)明:參數(shù)的概念前面有描述
// 這邊重點(diǎn)介紹下action的參數(shù):
// 自己可以輸出t看結(jié)構(gòu):
// [arg1,arg2,arg3,{},currentCommandRef]
// 如果第一的時(shí)候是兩個(gè)參數(shù)坟冲,則為
// [arg1,arg2,{},currentCommandRef]

// 輸入:node test cmd1
// 輸出:missing required argument 'arg1'
// 輸入:node test 1
// 輸出:child1 action call ['1',undefined,[],{},currentCommandRef]
// 輸入:node test 1 2 3 4
// 輸出:child1 action call ['1','2',['3','4'],{},currentCommandRef]
復(fù)制代碼
  • 命令 + 配置({isDefault:true})
program
.command('cmd1 <arg1> [arg2] [arg3...]',{isDefault:true})
.action((...t) => {
    console.log('child1 action call',t)
})
program.parse(process.argv); 

// 輸入:node test 1
// 輸出:child1 action call ['1',undefined,[],{},currentCommandRef]
// 說(shuō)明:也就是isdefault:會(huì)配置當(dāng)前命令為當(dāng)前層級(jí)的默認(rèn)命令
// 再說(shuō)一遍:當(dāng)前層級(jí)

// 案例二
program
.action((t) => {
    console.log('top action call')
})
.command('cmd1')
.action((t) => {
    console.log('child1 action call')
})
.command('cmd11',{isDefault:true})
.action((t) => {
    console.log('child11 action call')
})
program.parse(process.argv);

// 輸入:node test
// 輸出:top action call
// 輸入:node test cmd1
// 輸出:child11 action call

// 案例三
program
.action((t) => {
    console.log('top action call')
})
.command('cmd1',{isDefault:true})
.action((t) => {
    console.log('child1 action call')
})
.command('cmd11',{isDefault:true})
.action((t) => {
    console.log('child11 action call')
})
program.parse(process.argv); 

// 輸入:node test
// 輸出:child11 action call

  • 命令 + 配置({hidden: true})
program
.action((t) => {
    console.log('top action call')
})
.command('cmd1')
.action((t) => {
    console.log('child1 action call')
})
program.parse(process.argv);

// 輸入:node test --help
// 觀察:commands

program
.action((t) => {
    console.log('top action call')
})
.command('cmd1',{hidden:true})
.action((t) => {
    console.log('child1 action call')
})
program.parse(process.argv);

// 輸入:node test --help
// 觀察:commands
// 輸入:node test cmd1
// 輸出:child1 action call

// 總結(jié):{hidden:true},只是在--help時(shí)不把改命令暴露出去磨镶,但不影響正常使用
復(fù)制代碼
  • 命令 + 描述(description)
// 案例一:主命令 + description
program
.description('主命令描述')
.action((...t) => {
    console.log('top action call',t)
})
.parse(process.argv);

// 輸入:node test --help
// 輸出:主命令描述

// 案例二:主命令接收參數(shù)
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述')
.action((...t) => {
    console.log('top action call',t)
})
.parse(process.argv); 

// 輸入:node test 1
// 輸出:top action call ['1',undefined,undefined,{},currentCommandRef]

// 案例三:主命令接受參數(shù) + 描述參數(shù)
program
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述',{
    arg1:'這個(gè)是arg1 的描述',
    arg2:'這個(gè)是arg2 的描述',
    arg3:'這個(gè)是arg3 的描述', 
})
.action((...t) => {
    console.log('top action call',t)
})
.parse(process.argv); 

// 輸入:node test --help
// 輸出:
// 主命令描述
// Arguments:
//   arg1        這個(gè)是arg1 的描述
//   arg2        這個(gè)是arg2 的描述
//   arg3        這個(gè)是arg3 的描述

// 案例四:子命令 + 描述
program
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述',{
    arg1:'這個(gè)是arg1 的描述',
    arg2:'這個(gè)是arg2 的描述',
    arg3:'這個(gè)是arg3 的描述', 
})
.action((...t) => {
    console.log('top action call',t)
})
.command('cmd1 <carg1> [carg2]')
.description('子命令1描述',{
    carg1:'這個(gè)是carg1 的描述',
    carg2:'這個(gè)是carg2 的描述',
})
program.parse(process.argv); 

// 輸入:node test --help
// 輸入:node test cmd1 --help
// 總結(jié):也就是為當(dāng)前層的命令及參數(shù)增加描述介紹

2) 選項(xiàng)

使用option方法定義,可以理解為命令行數(shù)據(jù)結(jié)構(gòu)健提。

該函數(shù)很簡(jiǎn)單琳猫,可以方便的將文本輸入轉(zhuǎn)化為程序需要的數(shù)據(jù)形式。其功能如下:

  • 可以設(shè)置任何數(shù)量的選項(xiàng)私痹,每一個(gè)對(duì)應(yīng)一個(gè).option函數(shù)調(diào)用;
  • 可以設(shè)置默認(rèn)值;
  • 可以提供文本脐嫂、數(shù)值、數(shù)組紊遵、集合和范圍等約束類型(通過(guò)提供處理函數(shù));
  • 可以使用正則表達(dá)式;

說(shuō)明option方法账千,基本使用就用選項(xiàng)名稱和描述;復(fù)雜一點(diǎn)就要提供處理函數(shù)或默認(rèn)值;再?gòu)?fù)雜就用arguments方法代替option方法,使用可變參數(shù)(帶...的參數(shù))暗膜。

  • 理解option(基本概念)
program
.option('-a,--add','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test --help
// 觀察options:
Options:
  -a,--add    add something
  -h, --help  display help for command
// 輸入:node test
// 輸出:{}
// 輸入:node test -a
// 輸出:{ add: true }

// 說(shuō)明:options是用于注冊(cè)選項(xiàng)
// 語(yǔ)法:option('-短描述 --長(zhǎng)描述 參數(shù)1 參數(shù)2 參數(shù)3','描述',[入?yún)⒏袷胶瘮?shù)]匀奏,[迭代初始值])
// 對(duì)比命令command用action來(lái)執(zhí)行回調(diào),選項(xiàng)則使用.opts()來(lái)獲取選項(xiàng)值
// 對(duì)比命令学搜,同一個(gè)選項(xiàng)可以使用多次娃善,但命令只是第一次有效
// 對(duì)于選項(xiàng),我們可以把它理解為reduce瑞佩,特別是:
// [入?yún)⒏袷胶瘮?shù)]聚磺,[迭代初始值] 和 recude(cb(),defaultValue) 一樣理解就好

  • 選項(xiàng) + 參數(shù)(接收單個(gè)參數(shù))
program
.option('-a,--add <arg1>','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a 1
// 輸出:{ add: '1' }

program
.option('-a,--add [arg1] [arg2]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -a 1 2
// 輸出:{ add: '1' }
// 說(shuō)明:和命令的參數(shù)對(duì)比,選項(xiàng)只有定義了一個(gè)參數(shù)炬丸,不能arg1 arg2
// 思考:那要接收多個(gè)參數(shù)怎么辦呢瘫寝???

  • 選項(xiàng) + 參數(shù)(接收多個(gè)參數(shù))
// 方案一
program
.option('-a,--add [arg1...]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -a 1 2 3
// 輸出:{ add: [ '1', '2', '3' ] }
// 輸入:node test -a
// 輸出:{ add: true }
// 說(shuō)明:和命令的參數(shù)對(duì)比,如果是可選參數(shù),且是...矢沿,不傳參數(shù)的值不是[]而是默認(rèn)值

// 方案二
program
.option('-a,--add [arg1...]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a 1
// 輸出:{ add: ['1'] }
// 輸入:node test -a 1 -a 2
// 輸出:{ add: [ '1', '2' ] }
// 說(shuō)明:也就是如果參數(shù)是...滥搭,值會(huì)疊加,否則值會(huì)覆蓋
// 說(shuō)明:對(duì)比命令捣鲸,同一個(gè)選項(xiàng)可以使用多次瑟匆,但命令只是第一次有效

  • 選項(xiàng) + 參數(shù) + 入?yún)⒏袷胶瘮?shù)
// 案例一
const addFormat = (value,Accumulator) => {
    console.log(value,Accumulator)
    return value
}
program
.option('-a,--add [arg1]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a 1
// 輸出:
1 undefined
{ add: '1' }

// 案例二
const addFormat = (value,Accumulator) => {
    console.log(value,Accumulator)
    return '我來(lái)修改返回值' + value
}
program
.option('-a,--add [arg1]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -a 1
// 輸出:
1 undefined
{ add: '我來(lái)修改返回值1' }
// 輸入:node test -a 1 -a 2
// 輸出:
1 undefined
2 我來(lái)修改返回值1
{ add: '我來(lái)修改返回值2' }
// 說(shuō)明:我們發(fā)現(xiàn),第一次執(zhí)行返回的值會(huì)作為第二次執(zhí)行參數(shù)時(shí)候的第二個(gè)參數(shù)值
// 我們可以用:reduce來(lái)理解這個(gè)函數(shù)

// 案例三(加深對(duì) 入?yún)⒏袷胶瘮?shù) 的作用對(duì)象 的理解)
const addFormat = (value,Accumulator) => {
    console.log(value,Accumulator)
    return '我來(lái)修改返回值' + value
}
program
.option('-a,--add [arg1...]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a 1 2 -a 3 4
// 輸出:
1 undefined
2 我來(lái)修改返回值1
3 我來(lái)修改返回值2
4 我來(lái)修改返回值3
{ add: '我來(lái)修改返回值4' }
// 說(shuō)明:我們發(fā)現(xiàn)入?yún)⒏袷胶瘮?shù)作用于每個(gè)參數(shù)

  • 選項(xiàng) + 參數(shù) + 默認(rèn)值
program
.option('-a,--add [arg1]','add something',1)
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a
// 輸出:{ add: 1 }
// 說(shuō)明:我們發(fā)現(xiàn)輸入的都是string類型栽惶,這邊輸出取的是默認(rèn)值1
// 也就是如果沒(méi)有設(shè)置默認(rèn)值愁溜,默認(rèn)的默認(rèn)值為true

  • 選項(xiàng) + 參數(shù) + 入?yún)⒏袷胶瘮?shù) + 默認(rèn)值
const addFormat = (value,Accumulator) => {
    console.log(value,Accumulator)
    return value
}
program
.option('-a,--add [arg1]','add something',addFormat,1)
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -a
// 輸出:{ add: 1 }
// 輸入:node test -a 2
// 輸出:
2 1
{ add: '2' }

  • 選項(xiàng) + 參數(shù) + 入?yún)⒏袷胶瘮?shù)(具體案例加深理解)
// 案例一、入?yún)⒆址愋?=> 其他類型
const intFormat = (value,Accumulator) => {
    const formatValue = parseInt(value,10)
    if(isNaN(formatValue)) {
        throw new program.InvalidOptionArgumentError('Not a number.');
    } 
   return formatValue
}
program
.option('-i,--integer [arg1...]','to integer',intFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)

// 輸入:node test -i xx
// 輸出:
error: option '-i,--integer [arg1...]' argument 'ad' is invalid. Not a number.
// 輸入:12.6
// 輸出:{ integer: 12 }

// 案例二外厂、入?yún)?=> 其他格式
const splitFormat = (value,Accumulator) => {
 return value.split(', ')
}
program
.option('-s,--split [arg1...]','to integer',splitFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -s 1,2,3
// 輸出:{ split: [ '1,2,3' ] }

// 案例三冕象、迭代
const collectFormat = (value,Accumulator) => {
 Accumulator.push(value)
 return Accumulator
}
program
.option('-c,--collect [arg1...]','to integer',collectFormat,[])
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -c 1 2 -c 3
// 輸出:{ collect: [ '1', '2', '3' ] }

// 案例四、迭代
const statisticsFormat = (value,Accumulator) => {
    Accumulator +=1
    console.log('currentValue',value,'execCounter',Accumulator)
    return Accumulator
}
program
.option('-s,--statistics [arg1...]','to integer',statisticsFormat,0)
.parse(process.argv);
const options = program.opts()
console.log(options)
// 輸入:node test -s a b -s c
// 輸出:
currentValue a execCounter 1
currentValue b execCounter 2
currentValue c execCounter 3
{ statistics: 3 }

  • version:我們可以理解為簡(jiǎn)化版的選項(xiàng)
program
.version('0.1.1','-v,--version','版本描述')
.parse(process.argv)

// 輸入:node test -v
// 輸出:0.1.1
// 作用:用于標(biāo)記當(dāng)前命令的版本
// 輸入:node test --help
// 觀察:Options
Options:
  -v,--version  版本描述
  -h, --help    display help for command

  • help:我們可以理解為簡(jiǎn)化版的選項(xiàng)
// 觀察??
// 具體修改沒(méi)有測(cè)試汁蝶,這邊主要介紹概念

3)描述

  • 命令的描述
program
.description('這個(gè)是top命令的描述')
program.parse()
// 輸入:node test --help
// 觀察:這個(gè)是top命令的描述

program
.command('cmd1')
.description('這個(gè)是child1命令的描述')
program.parse()
// 輸入:node test --help
// 觀察:
Commands:
  cmd1            這個(gè)是child1命令的描述
// 說(shuō)明:description渐扮、arguments也都可以為子命令服務(wù)

program
.command('cmd1','這個(gè)是child1命令的描述')
program.parse()
// 輸入:node test --help
// 觀察:
Commands:
  cmd1            這個(gè)是child1命令的描述

  • 選項(xiàng)的描述
program
.option('cmd1','這個(gè)是選項(xiàng)的描述')
program.parse()

// 輸入:node test --help
// 觀察:
Options:
  cmd1        這個(gè)是選項(xiàng)的描述

  • 參數(shù)的描述
// 案例一、主命令 參數(shù)的描述
program
.arguments('[arg1] <arg2>')
.description('這個(gè)是top命令的描述',{
    arg1:'arg1 的描述',
    arg2:'arg2 的描述'
})
program.parse()
// 輸入:node test --help
// 觀察:
這個(gè)是top命令的描述

Arguments:
  arg1        arg1 的描述
  arg2        arg2 的描述

// 案例二掖棉、子命令 參數(shù)的描述
program
.command('cmd1 [cArg1] [cArg2]')
.description('這個(gè)是child1命令的描述',{
    cArg1:'cArg1 的描述',
    cArg2:'cArg2 的描述'
})
program.parse()
// 輸入:node test cmd1 --help
// 觀察:
這個(gè)是child1命令的描述

Arguments:
  cArg1       cArg1 的描述
  cArg2       cArg2 的描述

4)幫助

使用help方法輸出一切有用的描述信息墓律,這些信息通常在命令和選項(xiàng)的定義中,例如

program.help();

如果要定制幫助信息幔亥,就用:

program.on('--help',cb);

5)邏輯

使用action方法注冊(cè)邏輯戒傻,將代碼轉(zhuǎn)向執(zhí)行自己的邏輯代碼尝哆,當(dāng)然污桦,git類型的多命令也可以不用衰抑。例如

program.action(function(cmd, options) { //code });

3.3 第3步:開(kāi)發(fā)業(yè)務(wù)邏輯

撰寫action可以調(diào)用的代碼就是了。

五香伴、Node.js進(jìn)程管理

1慰枕、簡(jiǎn)介

process是一個(gè)全局內(nèi)置對(duì)象,可以在代碼中的任何位置訪問(wèn)此對(duì)象即纲,這個(gè)對(duì)象代表我們的node.js代碼宿主的操作系統(tǒng)進(jìn)程對(duì)象捺僻。

使用process對(duì)象可以截獲進(jìn)程的異常、退出等事件崇裁,也可以獲取進(jìn)程的當(dāng)前目錄匕坯、環(huán)境變量、內(nèi)存占用等信息拔稳,還可以執(zhí)行進(jìn)程退出葛峻、工作目錄切換等操作。

2巴比、cwd函數(shù)的基本用法

當(dāng)我們想要查看應(yīng)用程序當(dāng)前目錄時(shí)术奖,可以使用cwd函數(shù)礁遵,使用語(yǔ)法如下:

process.cwd();

3、chdir函數(shù)的基本用法

如果需要改變應(yīng)用程序目錄采记,就要使用chdir函數(shù)了佣耐,它的用法如下:

process.chdir("目錄");

4、stdout的基本用法

stdout是標(biāo)準(zhǔn)輸出流唧龄,它是干什么的呢兼砖?請(qǐng)下看下面的示例:

console.log = function(d){
    process.stdout.write(d+'\n');
}

沒(méi)錯(cuò),它的作用就是將內(nèi)容打印到輸出設(shè)備上既棺,console.log就是封裝了它讽挟。

5、stderr的基本用法

stderr是標(biāo)準(zhǔn)錯(cuò)誤流丸冕,和stdout的作用差不多耽梅,不同的是它是用來(lái)打印錯(cuò)誤信息的,我們可以通過(guò)它來(lái)捕獲錯(cuò)誤信息胖烛,基本使用方法如下:

process.stderr.write(輸入內(nèi)容);

6眼姐、stdin的基本用法

stdin是進(jìn)程的輸入流,我們可以通過(guò)注冊(cè)事件的方式來(lái)獲取輸入的內(nèi)容,如下:

process.stdin.on('readable', function() {
  var chunk = process.stdin.read();
  if (chunk !== null) {
    process.stdout.write('data: ' + chunk);
  }
});

示例中的chunk就是輸入流中的內(nèi)容佩番。

7众旗、exit函數(shù)的基本用法

如果你需要在程序內(nèi)殺死進(jìn)程,退出程序答捕,可以使用exit函數(shù),示例如下:

process.exit(code);

參數(shù)code為退出后返回的代碼屑那,如果省略則默認(rèn)返回0拱镐;

8、監(jiān)聽(tīng)進(jìn)程事件

使用process.on() 方法可以監(jiān)聽(tīng)進(jìn)程事件持际。

exit事件

當(dāng)進(jìn)程要退出之前沃琅,會(huì)觸發(fā)exit事件。通過(guò)監(jiān)聽(tīng)exit事件蜘欲,我們可就以在進(jìn)程退出前進(jìn)行一些清理工作:

//參數(shù)code表示退出碼
process.on("exit",function(code){
  //進(jìn)行一些清理工作
  console.log("I am tired...")
});
var tick = Date.now();
console.log(tick);

uncaughtException事件

如果進(jìn)程發(fā)生了未捕捉的異常益眉,會(huì)觸發(fā)uncaughtException事件。通過(guò)監(jiān)聽(tīng)這個(gè)事件姥份,你可以 讓進(jìn)程優(yōu)雅的退出:

//參數(shù)err表示發(fā)生的異常
process.on("uncaughtException",function(err){
  console.log(err);
});
//故意拋出一個(gè)異常
throw new Error("我故意的...");

9郭脂、設(shè)置編碼

在我們的輸入輸出的內(nèi)容中有中文的時(shí)候,可能會(huì)亂碼的問(wèn)題澈歉,這是因?yàn)榫幋a不同造成的展鸡,所以在這種情況下需要為流設(shè)置編碼,如下示例:

process.stdin.setEncoding(編碼);

process.stdout.setEncoding(編碼);

process.stderr.setEncoding(編碼);

10埃难、process.argv

process 對(duì)象是一個(gè)全局變量莹弊,它提供當(dāng)前 Node.js 進(jìn)程的有關(guān)信息涤久,以及控制當(dāng)前 Node.js 進(jìn)程。 因?yàn)槭侨肿兞咳坛冢詿o(wú)需使用 require()响迂。

process.argv 屬性返回一個(gè)數(shù)組,這個(gè)數(shù)組包含了啟動(dòng)Node.js進(jìn)程時(shí)的命令行參數(shù)细疚,

其中:

  • 數(shù)組的第一個(gè)元素process.argv[0]——返回啟動(dòng)Node.js進(jìn)程的可執(zhí)行文件所在的絕對(duì)路徑

  • 第二個(gè)元素process.argv[1]——為當(dāng)前執(zhí)行的JavaScript文件路徑

  • 剩余的元素為其他命令行參數(shù)

例如:

輸入命令:node scripts/build.js "web-runtime-cjs,web-server-renderer"
結(jié)果:

console.log(process.argv[0]) // 打印 D:\nodeJs\node.exe
console.log(process.argv[1]) // 打印 E:\Study_document\vue-resource\vue-dev\scripts\build.js
console.log(process.argv[2]) // 打印 web-runtime-cjs,web-server-renderer

六蔗彤、Node.js常用模塊

1、async模塊

async模塊是為了解決嵌套金字塔,和異步流程控制而生.常用的方法介紹

npm 安裝好async模塊,然后引入就可以使用 var async = require('async');

1.1 series(tasks,[callback])

多個(gè)函數(shù)從上到下依次執(zhí)行,相互之間沒(méi)有數(shù)據(jù)交互

var async = require('async');
var task1 = function(callback){
  console.log("task1");
  callback(null,"task1")
}
var task2 = function(callback){
  console.log("task2");
  callback(null,"task2")
}
var task3 = function(callback){
  console.log("task3");
  callback(null,"task3")
}

async.series([task1,task2,task3],function(err,result){
  console.log("series");
  if (err) {
    console.log(err);
  }
    console.log(result);
})
輸出結(jié)果:
task1
task2
task3
series
[ 'task1', 'task2', 'task3' ]

如果中途發(fā)生錯(cuò)誤,則將錯(cuò)誤傳遞到回調(diào)函數(shù),并停止執(zhí)行后面的函數(shù)

1.2 parallel(tasks,[callback])

多個(gè)函數(shù)并行執(zhí)行,不會(huì)等待其他函數(shù)

var async = require('async')
var task1 =function(callback){
  console.log("task1");
  setTimeout(function(){
    callback(null,"task1")
  },5000); 
}

var task2 =function(callback){
  console.log("task2");
  setTimeout(function(){
    callback(null,"task2")
  },1000);
}

var task3 =function(callback){
  console.log("task3");
  setTimeout(function(){
    callback(null,"task3")
  },3000);
}

console.time("parallel方法");

async.parallel([task1,task2,task3],function(err,result){
  console.log("parallel");
  if (err) {
    console.log(err);
  }
  console.log(result);
  console.timeEnd("parallel方法");
})
輸出結(jié)果:
task1
task2
task3
parallel
[ 'task1', 'task2', 'task3' ]
parallel方法: 5.017s

3個(gè)函數(shù)分別延遲5000ms,1000ms,3000ms 結(jié)果5000ms就執(zhí)行完畢.

如果中途出錯(cuò),則立即將err和值傳到最終的回調(diào)函數(shù),其他未執(zhí)行完畢的函數(shù)將不再執(zhí)行,但是要占一個(gè)位置

1.3 waterfall(tasks,[callback]) :瀑布流

依次執(zhí)行,前一個(gè)函數(shù)的輸出為后一個(gè)函數(shù)的輸入

var async = require('async');

var task1 =function(callback){
  console.log("task1");
  callback(null,"11")
}

var task2 =function(q,callback){
  console.log("task2");
  console.log("task1函數(shù)傳入的值: "+q);
  callback(null,"22")
}

var task3 =function(q,callback){
  console.log("task3");
  console.log("task2函數(shù)傳入的值: "+q);
  callback(null,"33")
}

console.time("waterfall方法");
async.waterfall([task1,task2,task3],function(err,result){
  console.log("waterfall");
  if (err) {
    console.log(err);
  }
  console.log("result : "+result);
  console.timeEnd("waterfall方法");
})
輸出結(jié)果:
task1
task2
task1函數(shù)傳入的值: 11
task3
task2函數(shù)傳入的值: 22
waterfall
result : 33
waterfall方法: 3.219ms

如果中途出現(xiàn)錯(cuò)誤,后面的函數(shù)將不在執(zhí)行,之前執(zhí)行的結(jié)果和錯(cuò)誤信息將直接傳到最終的回調(diào)函數(shù)

1.4 parallelLimit(tasks,limit,[callback])

和parallel類似,只是limit參數(shù)限制了同時(shí)并發(fā)執(zhí)行的個(gè)數(shù),不再是無(wú)限并發(fā)

var async  = require('async')
var task1 =function(callback){
  console.log("task1");
  setTimeout(function(){
    callback(null,"task1")
  },5000);
  
}

var task2 =function(callback){
  console.log("task2");
  setTimeout(function(){
    callback(null,"task2")
  },3000);
}

var task3 =function(callback){
  console.log("task3");
  setTimeout(function(){
    callback(null,"task3")
  },4000);
}

console.time("parallelLimit方法");
async.parallelLimit([task1,task2,task3], 2, function(err,result){
  console.log("parallelLimit");
  if (err) {
    console.log(err);
  }
  console.log(result);
  console.timeEnd("parallelLimit方法");
})
輸出結(jié)果:
task1
task2
task3
parallelLimit
[ 'task1', 'task2', 'task3' ]
parallelLimit方法: 7.018s

三個(gè)函數(shù)分別是延遲5000ms,3000ms,4000ms結(jié)果執(zhí)行時(shí)間為什么是7000ms呢

因?yàn)槭紫葓?zhí)行函數(shù)1和2,

3秒后函數(shù)2執(zhí)行完畢,這個(gè)時(shí)候函數(shù)3開(kāi)始執(zhí)行,

5秒后函數(shù)1執(zhí)行完畢,函數(shù)3還有2秒,

7秒后函數(shù)3執(zhí)行完畢.

1.5 auto(tasks,[callback])

多個(gè)函數(shù)有數(shù)據(jù)交互,有的并行,有的依次執(zhí)行

var async = require('async')
console.time("auto方法");
    async.auto({
    task1: function(callback){
        console.log("tsak1");
        setTimeout(function(){
          callback(null, 'task11', 'task12');
        },2000);
    },
    task2: function(callback){ 
        console.log('task2');
        setTimeout(function(){       
          callback(null, 'task2');
        },3000);
    },
    task3: ['task1', 'task2', function(callback, results){
        console.log('task3');
        console.log('task1和task2運(yùn)行結(jié)果: ',results);
        setTimeout(function(){        
          callback(null, 'task3');
        },1000);
    }],
    task4: ['task3', function(callback, results){
        console.log('task4');
        console.log('task1,task2,task3運(yùn)行結(jié)果: ',results);
        setTimeout(function(){
        callback(null, {'task41':results.task3, 'task42':'task42'});
      },1000);
    }]
    }, function(err, results) {
    console.log('err :', err);
    console.log('最終results : ', results);
    console.timeEnd("auto方法");
    });

5秒運(yùn)行完畢,

函數(shù)1和2并行,3秒執(zhí)行完畢,

函數(shù)1和2執(zhí)行完畢后,函數(shù)3,4依次執(zhí)行共計(jì)5秒.

1.6 whilst(test,fn,[callback])

相當(dāng)于while循環(huán),fn函數(shù)里不管是同步還是異步都會(huì)執(zhí)行完上一次循環(huán)才會(huì)執(zhí)行下一次循環(huán),對(duì)異步循環(huán)很有幫助,

test是條件,為true時(shí)執(zhí)行fn里的方法

var async = require('async')
var datalist = [{number:10},{number:20},{number:30},{number:40},{number:50}];
    var count = 0;
    var test = function () {
        return count<datalist.length;
    };
    var fn = function(callback){
        console.log(datalist[count].number);
        setTimeout(function () {
            count++;
            callback();
        },1000)
    };
    
    async.whilst(test,fn,function(err){
        if(err){
            console.log(err);
        }
        console.log('whilst結(jié)束');
    });

1.7 doWhilst

和whilst類似,和do-while一個(gè)意思,首先執(zhí)行一次fn,再判斷,和whilst相比它把fn和test位置交換了而已.

until和whilst相反,當(dāng)test判斷為false的時(shí)候執(zhí)行fn里的方法,為true時(shí)跳出,

doUntil與doWhilst相反.

1.8 forever(fn,errback)

forever就是無(wú)限循環(huán)了.只有當(dāng)中途出現(xiàn)錯(cuò)誤的時(shí)候才會(huì)停止

1.9 compose(fn1,fn2,fn3...)

這個(gè)方法會(huì)創(chuàng)建一個(gè)異步的集合函數(shù),執(zhí)行的順序是倒序.前一個(gè)fn的輸出是后一個(gè)fn的輸入.有數(shù)據(jù)交互

var async = require('async')

var task1 =function(m,callback){
  console.log("task1");
  setTimeout(function(){
    callback(null,m*2)
  },1000); 
}

var task2 =function(m,callback){
  console.log("task2");
  setTimeout(function(){
    callback(null,m+3)
  },1000);
}

var task3 =function(m,callback){
  console.log("task3");
  setTimeout(function(){
    callback(null,m*5)
  },1000);
}
console.time("compose方法");
var com = async.compose(task3,task2,task1);

com(2,function(err,result){
  if (err) {
    console.log(err);
  }
  console.log(result);
  console.timeEnd("compose方法");
})
輸出結(jié)果:
task1
task2
task3
35
compose方法: 3.038s

相當(dāng)于 var m=2; (m2+3)5 =35;

1.10 parallelLimit 和 eachLimit

1) 概述

async.parallelLimit 方法在文檔中位于 Controll Flow 章節(jié)惠昔,表明這個(gè)方法是用來(lái)做流程控制的幕与,async.eachLimit 方法位于 Collections 章節(jié),表明這個(gè)方法是用來(lái)做數(shù)據(jù)處理的镇防。在實(shí)際開(kāi)發(fā)中我們可以使用這兩個(gè)方法來(lái)完成同樣的工作啦鸣,下面我們就以給 26 位用戶發(fā)送郵件這個(gè)任務(wù)來(lái)舉例。

2) 使用 async.parallelLimit 方法實(shí)現(xiàn)

async.parallelLimit 方法接受兩個(gè)參數(shù)来氧,第一個(gè)參數(shù)為任務(wù)數(shù)組诫给,每個(gè)任務(wù)是一個(gè)函數(shù),第二個(gè)參數(shù)為每次并行執(zhí)行的任務(wù)數(shù)啦扬,第三個(gè)參數(shù)為回調(diào)函數(shù)中狂。使用 async.parallelLimit 完成發(fā)送郵件任務(wù)的思路是先使用數(shù)據(jù)與所要做的任務(wù),組裝成任務(wù)數(shù)組交給 async.parallelLimit 方法去執(zhí)行扑毡。

let userEmailList = [ 'a@example.com', 'b@example.com', ..., 'z@example.com' ];
let limit = 5;
let taskList = userEmailList.map(function (email) {
    return function (callback) {
        sendEmail(email, function (error, result) {
            return callback(error, result);
        });
    }
});
async.parallel(taskList, limit, function (error, result) {
    console.log(error, result);
});

3) 使用 async.eachLimit 方法實(shí)現(xiàn)

async.eachLimit 方法接受四個(gè)參數(shù)胃榕,第一個(gè)參數(shù)為原始數(shù)據(jù)數(shù)組,第二個(gè)參數(shù)為每次并行處理的數(shù)據(jù)量瞄摊,第三個(gè)參數(shù)為需要為數(shù)據(jù)進(jìn)行的處理勋又,第四個(gè)參數(shù)為回調(diào)函數(shù)。使用 async.eachLimit 完成發(fā)送郵件任務(wù)的思路是定義一個(gè)對(duì)數(shù)據(jù)進(jìn)行處理的函數(shù)换帜,然后使用 async.eachLimit 將處理函數(shù)應(yīng)用所有數(shù)據(jù)上楔壤。

let userEmailList = [ 'a@example.com', 'b@example.com', ..., 'z@example.com' ];
let limit = 5;
let processer = function (email) {
    sendEmail(email, function (error) {
        return callback(error, result);
    });
}
async.eachLimit(userEmailList, limit, processer, function (error, result){
    console.log(error);
});

通過(guò)以上代碼和 async 文檔 可以看出 each 系列函數(shù)最終的回調(diào)函數(shù)是沒(méi)有運(yùn)行結(jié)果的,所以每一次 processor 中的結(jié)果需要另行存儲(chǔ)處理惯驼。

4) 總結(jié)

通過(guò)對(duì)比以上兩種方案蹲嚣,很容易發(fā)現(xiàn) async.parallelLimitasync.eachLimit 的區(qū)別與應(yīng)用場(chǎng)景,async.parallelLimit 作為流程控制方法祟牲,應(yīng)該應(yīng)用于并發(fā)處理不同的任務(wù)并返回結(jié)果隙畜,async.eachLimit 作為數(shù)據(jù)處理方法,應(yīng)該應(yīng)用于并發(fā)地對(duì)一批數(shù)據(jù)進(jìn)行相同的處理说贝。所以顯然對(duì)于給 26 為用戶發(fā)送郵件這個(gè)任務(wù)應(yīng)該使用 async.eachLimit 方法來(lái)實(shí)現(xiàn)禾蚕。

在應(yīng)用場(chǎng)景選擇恰當(dāng)?shù)那闆r下很少使用到 async.parallelLimit 方法,使用 async.parallel 就可以了狂丝,畢竟任務(wù)數(shù)量不會(huì)非常巨大换淆,不做限制一次性并行執(zhí)行也不會(huì)有太大問(wèn)題哗总。但是如果使用不當(dāng),用作數(shù)據(jù)處理倍试,數(shù)據(jù)的量級(jí)可能會(huì)非常巨大讯屈,如果不做并行數(shù)量限制顯然是不可取的方式。

因?yàn)閷?duì)于這兩個(gè)方法理解地不夠透徹县习,并且受到 Promise.all 使用方式的影響涮母,很多歷史代碼中從未出現(xiàn)過(guò) async.eachLimit,都是使用 async.parallelLimit 配合 map 笨拙的實(shí)現(xiàn)了功能躁愿。

2叛本、config包(配置文件管理)

2.1基本使用

在程序部署過(guò)程中,不同的環(huán)境(生產(chǎn)環(huán)境彤钟、開(kāi)發(fā)環(huán)境)程序的一些配置參數(shù)不同来候,比如數(shù)據(jù)庫(kù)信息配置。

對(duì)于配置的參數(shù)逸雹,我們通常使用配置文件管理营搅。

在nodejs中,可以使用第三方模塊config模塊管理操作配置文件梆砸。

  • config模塊的作用

允許開(kāi)發(fā)人員將不同運(yùn)行環(huán)境下的應(yīng)用配置信息抽離到單獨(dú)的文件中转质,模塊內(nèi)部自動(dòng)判斷當(dāng)前應(yīng)用的運(yùn)行環(huán)境(環(huán)境變量配置的-NODE_ENV的值),并讀取對(duì)應(yīng)的配置信息帖世,極大提供應(yīng)用配置信息的維護(hù)成本休蟹,避免了當(dāng)運(yùn)行環(huán)境重復(fù)的多次切換時(shí),手動(dòng)到項(xiàng)目代碼中修改配置信息日矫。

  • 使用步驟
  1. 使用npm install config命令下載模塊
  2. 在項(xiàng)目的根目錄下新建config文件夾
  3. 在config文件夾下新建default.json赂弓、development.json、production.json文件
  4. 在項(xiàng)目中通過(guò)require方法搬男,將模塊導(dǎo)入程序中
  5. 使用模塊內(nèi)部提供的get方法獲取配置信息
  • 代碼示例:

default.json

{
  "title":"管理系統(tǒng)"
}

production.json

{
  "title":"管理系統(tǒng)-生產(chǎn)環(huán)境"
}

development.json

{
  "title":"管理系統(tǒng)-開(kāi)發(fā)環(huán)境", 
   "db": {
    "user":"root",
    "pwd": "root",
    "host":"127.0.0.1",
    "port":28888,
    "datasource": "myblog"
  }
}

app.js

const config = require('config')
const title = config.get('title')
console.log(title)
const host = config.get('db.host')
console.log(host)
console.log(process.env.NODE_ENV)

輸出結(jié)果

管理系統(tǒng)-開(kāi)發(fā)環(huán)境
127.0.0.1
development

2.2 擴(kuò)展

在實(shí)際開(kāi)發(fā)中拣展,對(duì)于一些敏感配置信息(如:數(shù)據(jù)庫(kù)登錄密碼)彭沼,我們一般不會(huì)選擇明文寫在配置文件中缔逛,而是配置在系統(tǒng)變量中。操作步驟如下

  1. 在config文件夾中建立custom-environment-variables.json文件姓惑。
  2. 配置項(xiàng)屬性的值填寫系統(tǒng)環(huán)境變量的名字
  3. 項(xiàng)目運(yùn)行時(shí)config模塊查找系統(tǒng)環(huán)境變量褐奴,并讀取其值作為配置項(xiàng)的值。

custom-environment-variables.json文件

[圖片上傳失敗...(image-ef422c-1648865490772)]

{
  "db": {
   "pwd": "MYBLOGDB_PWD"
 }
}

app.js

const config = require('config')
console.log(process.env.NODE_ENV)
console.log(config.get('db.pwd'))

輸出結(jié)果:

development
123456 

3于毙、Crypto模塊

3.1 下載加密庫(kù)

npm install crypto

常見(jiàn)的摘要算法 與 對(duì)應(yīng)的輸出位數(shù):

  • MD5:128位
  • SHA-1:160位
  • SHA256 :256位
  • SHA512:512位

3.2 MD5加密

1)概述:
MD5消息摘要算法敦冬,屬Hash算法一類。MD5算法對(duì)輸入任意長(zhǎng)度的消息進(jìn)行運(yùn)行唯沮,產(chǎn)生一個(gè)128位的消息摘要(32位的數(shù)字字母混合碼)脖旱。

2)MD5主要特點(diǎn):
不可逆堪遂,相同數(shù)據(jù)的MD5值肯定一樣,不同數(shù)據(jù)的MD5值不一樣

//引入crypto模塊
var crypto = require('crypto'); 

var md5 = crypto.createHash('md5');  
var message = 'hello';
var digest = md5.update(message, 'utf8').digest('hex'); //hex轉(zhuǎn)化為十六進(jìn)制
console.log(digest);
// 輸出如下:注意這里是16進(jìn)制
// 5d41402abc4b2a76b9719d911017c592

3)crypto.createHash()方法用于創(chuàng)建一個(gè)哈希對(duì)象萌庆,該哈希對(duì)象可通過(guò)使用所述算法創(chuàng)建哈希摘要溶褪。

  • 用法:

    crypto.createHash( algorithm, options )
    
  • 參數(shù): 此方法接受兩個(gè)參數(shù),如avobe所述践险,如下所述:

    • algorithm: 它取決于平臺(tái)上的OpenSSL版本所支持的可訪問(wèn)算法猿妈。它返回字符串。示例是sha256巍虫,sha512等彭则。

    • options: 它是可選參數(shù),用于控制流的行為占遥。它返回一個(gè)對(duì)象俯抖。此外,對(duì)于XOF哈希函數(shù)(如“ shake256”)筷频,選項(xiàng)outputLength可用于確定所需的輸出長(zhǎng)度(以字節(jié)為單位)蚌成。

    • 返回類型: 它返回哈希對(duì)象。

4)hash.update()方法是加密模塊的Hash類的內(nèi)置函數(shù)凛捏。這用于用給定的數(shù)據(jù)更新哈希担忧。可以多次調(diào)用此方法以更新哈希的內(nèi)容坯癣,因?yàn)榇朔椒梢垣@取流數(shù)據(jù)瓶盛,例如文件讀取流。

此函數(shù)將數(shù)據(jù)作為生成哈希的參數(shù)示罗,它可以是字符串或文件對(duì)象惩猫。與數(shù)據(jù)一起,這也需要數(shù)據(jù)的編碼類型蚜点,可以是utf-8轧房,二進(jìn)制或ASCII。如果未提供編碼并且data是字符串绍绘,則使用utf-8奶镶。所需的輸出長(zhǎng)度(以字節(jié)為單位)。

  • 用法:

    hash.update(data [,Encoding])
    
  • 參數(shù): 此函數(shù)采用以下兩個(gè)參數(shù):

     data: 需要添加到哈希中的數(shù)據(jù)陪拘。
     encoding: 數(shù)據(jù)的編碼類型厂镇。
    
  • 返回值: 此方法返回具有更新數(shù)據(jù)的對(duì)象

5)hash.digest()方法是加密模塊的Hash類的內(nèi)置函數(shù)。這用于創(chuàng)建創(chuàng)建哈希時(shí)傳遞的數(shù)據(jù)的摘要左刽。

例如捺信,當(dāng)我們創(chuàng)建一個(gè)哈希時(shí),我們首先使用crypto.createHash()創(chuàng)建一個(gè)哈希實(shí)例欠痴,然后使用update()函數(shù)更新哈希內(nèi)容迄靠,但是直到現(xiàn)在我們還沒(méi)有得到結(jié)果哈希值秒咨,因此要獲得哈希值我們使用Hash類提供的摘要函數(shù)。

此函數(shù)將字符串作為輸入掌挚,該字符串定義返回值的類型拭荤,例如hex或base64。如果您離開(kāi)此字段疫诽,您將得到Buffer作為結(jié)果舅世。

  • 用法:
hash.digest([encoding])
  • 參數(shù): 此函數(shù)采用以下一個(gè)參數(shù):

    • encoding: 此方法采用一個(gè)可選參數(shù),該參數(shù)定義返回輸出的類型奇徒。您可以使用‘hex’或‘base64’作為參數(shù)雏亚。

模塊安裝:使用以下命令安裝所需的模塊:

npm install crypto

返回值: 傳遞參數(shù)時(shí),此函數(shù)返回String摩钙,而沒(méi)有傳遞參數(shù)時(shí)罢低,返回Buffer對(duì)象。假設(shè)我們傳遞了base64參數(shù)胖笛,那么返回值將是一個(gè)base64編碼字符串网持。

3.3 MAC加密

MAC(Message Authentication Code):消息認(rèn)證碼,用以保證數(shù)據(jù)的完整性长踊。運(yùn)算結(jié)果取決于消息本身功舀、秘鑰。
MAC可以有多種不同的實(shí)現(xiàn)方式身弊,比如HMAC辟汰。

HMAC(Hash-based Message Authentication Code):可以粗略地理解為帶秘鑰的hash函數(shù)。

MAC加密

const crypto = require('crypto');
// 參數(shù)一:摘要函數(shù)
// 參數(shù)二:秘鑰
let hmac = crypto.createHmac('md5', '123456');
let ret = hmac.update('hello').digest('hex');
console.log(ret);
// 9c699d7af73a49247a239cb0dd2f8139

3.4 對(duì)稱加密阱佛、非對(duì)稱加密

  • 加密/解密:給定明文帖汞,通過(guò)一定的算法,產(chǎn)生加密后的密文凑术,這個(gè)過(guò)程叫加密翩蘸。反過(guò)來(lái)就是解密

  • 秘鑰:為了進(jìn)一步增強(qiáng)加/解密算法的安全性,在加/解密的過(guò)程中引入了秘鑰淮逊。秘鑰可以視為加/解密算法的參數(shù)催首,在已知密文的情況下,如果不知道解密所用的秘鑰壮莹,則無(wú)法將密文解開(kāi)翅帜。

根據(jù)加密姻檀、解密所用的秘鑰是否相同命满,可以將加密算法分為對(duì)稱加密、非對(duì)稱加密绣版。

1)對(duì)稱加密

加密胶台、解密所用的秘鑰是相同的歼疮,即encryptKey === decryptKey。

常見(jiàn)的對(duì)稱加密算法:DES诈唬、3DES韩脏、AES、Blowfish铸磅、RC5赡矢、IDEA。

AES有很多不同的算法阅仔,如aes192吹散,aes-128-ecb,aes-256-cbc等

加八酒、解密偽代碼:

encryptedText = encrypt(plainText, key); // 加密
plainText = decrypt(encryptedText, key); // 解密

2)非對(duì)稱加密

又稱公開(kāi)秘鑰加密空民。加密、解密所用的秘鑰是不同的羞迷,即encryptKey !== decryptKey界轩。

加密秘鑰公開(kāi),稱為公鑰衔瓮。
解密秘鑰保密浊猾,稱為秘鑰。

常見(jiàn)的非對(duì)稱加密算法:RSA热鞍、DSA与殃、ElGamal。

加碍现、解密偽代碼:

encryptedText = encrypt(plainText, publicKey); // 加密
plainText = decrypt(encryptedText, priviteKey); // 解密

3)對(duì)比與應(yīng)用

除了秘鑰的差異幅疼,還有運(yùn)算速度上的差異。通常來(lái)說(shuō):

對(duì)稱加密速度要快于非對(duì)稱加密昼接。
非對(duì)稱加密通常用于加密短文本爽篷,對(duì)稱加密通常用于加密長(zhǎng)文本。
兩者可以結(jié)合起來(lái)使用慢睡,比如HTTPS協(xié)議逐工,可以在握手階段,通過(guò)RSA來(lái)交換生成對(duì)稱秘鑰漂辐。在之后的通訊階段泪喊,可以使用對(duì)稱加密算法對(duì)數(shù)據(jù)進(jìn)行加密,秘鑰則是握手階段生成的髓涯。
備注:對(duì)稱秘鑰交換不一定通過(guò)RSA袒啼,還可以通過(guò)類似DH來(lái)完成

4、util模塊

5、bulebird模塊

6蚓再、debug模塊打印調(diào)試日志

  • 安裝debug模塊
npm install debug
  • 使用很簡(jiǎn)單滑肉,運(yùn)行node程序時(shí),加上DEBUG=app環(huán)境變量即可
/**
 * debug基礎(chǔ)例子
 */
var debug = require('debug')('app');
 
// 運(yùn)行 DEBUG=app node 01.js
// 輸出:app hello +0ms
debug('hello');
  • 當(dāng)項(xiàng)目程序變得復(fù)雜摘仅,我們需要對(duì)日志進(jìn)行分類打印靶庙,debug支持命令空間,如下所示娃属。

DEBUG=app,api:表示同時(shí)打印出命名空間為app六荒、api的調(diào)試日志。

DEBUG=a*:支持通配符矾端,所有命名空間為a開(kāi)頭的調(diào)試日志都打印出來(lái)恬吕。

/**
 * debug例子:命名空間
 */
var debug = require('debug');
var appDebug = debug('app');
var apiDebug = debug('api');
 
// 分別運(yùn)行下面幾行命令看下效果
// 
//     DEBUG=app node 02.js
//     DEBUG=api node 02.js
//     DEBUG=app,api node 02.js
//     DEBUG=a* node 02.js
//     
appDebug('hello');
apiDebug('hello');
  • 有的時(shí)候,我們想要打印出所有的調(diào)試日志须床,除了個(gè)別命名空間下的铐料。這個(gè)時(shí)候,可以通過(guò)-來(lái)進(jìn)行排除豺旬,如下所示钠惩。-account*表示排除所有以account開(kāi)頭的命名空間的調(diào)試日志求厕。
/**
 * debug例子:排查命名空間
 */
var debug = require('debug');
var listDebug = debug('app:list');
var profileDebug = debug('app:profile');
var loginDebug = debug('account:login');
 
// 分別運(yùn)行下面幾行命令看下效果
// 
//     DEBUG=* node 03.js
//     DEBUG=*,-account* node 03.js
//     
listDebug('hello');
profileDebug('hello');
loginDebug('hello');
  • debug也支持格式化輸出屏歹,如下例子所示。
/**
 * debug:自定義格式化
 */
var createDebug = require('debug')
 
createDebug.formatters.h = function(v) {
  return v.toUpperCase();
};
 
var debug = createDebug('foo');
 
// 運(yùn)行 DEBUG=foo node 04.js 
// 輸出 foo My name is CHYINGP +0ms
debug('My name is %h', 'chying');

7瀑凝、validator 驗(yàn)證模塊

  • 安裝模塊
npm install validator
  • 引入
var validator = require('validator');
  • 驗(yàn)證介紹

    • contains(str, seed) : 是否包含字符串

    • equals(str, comparison) : 檢查字符串是否匹配

    • isAfter(str [, date]) : 檢查字符串是否在指定的日期之后,默認(rèn)是當(dāng)前日期

    • isBefore(str [, date]) 和 isAfter類似

    • isAlpha(str [, locale]) : 檢查字符串是否只包含(a-zA-Z).語(yǔ)言環(huán)境是其中之一
      ['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW',
      'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY',
      'ar-TN', 'ar-YE', 'cs-CZ', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN',
      'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'nl-NL',
      'pl-PL', 'pt-PT', 'ru-RU', 'tr-TR']
      默認(rèn)為:en-US

    • isAlphanumeric(str [, locale]) : 檢查字符串是否只包含字母和數(shù)字,語(yǔ)言環(huán)境和上面一樣,默認(rèn)也是 en-US

    • isAscii(str) : 檢查字符串是否只包含ASCII字符

    • isBase64(str) : 檢查字符串是否符合base64編碼格式

    • isBoolean(str) : 檢查字符串是否是boolean值

    • isByteLength(str, options) : 檢查字符串的長(zhǎng)度是否在一個(gè)范圍內(nèi),包含最大最小臨界值

    • isCreditCard(str) : 檢查字符串是否是信用卡

    • isCurrency(str, options) : 檢查字符串是否是有效金額,options是個(gè)對(duì)象 默認(rèn):{symbol: '$', require_symbol: false,allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false,negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false,thousands_separator: ',', decimal_separator: '.', allow_space_after_digits: false }.

    • isDataURI(str) : 檢查字符串是否是uri格式

    • isDate(str) : 檢查字符串是否是日期

    • isDecimal(str) : 檢查字符串是否是十進(jìn)制數(shù)

    • isDivisibleBy(str, number) : 檢查字符串是否是整除的數(shù)

    • isEmail(str [, options]) : 檢查字符串是否是郵箱 ,options是一個(gè)對(duì)象默認(rèn)為

      { allow_display_name: false, allow_utf8_local_part: true, require_tld: true }

    • isFQDN(str [, options]) : 檢查字符串是否完全限定域名 , options是個(gè)對(duì)象,默認(rèn):{ require_tld: true, allow_underscores: false,allow_trailing_dot: false }.

    • isFloat(str [, options]) : 檢查字符串是否是浮點(diǎn)數(shù), options是個(gè)對(duì)象,包含最大最小值,比如{min:0.5,max:10.5}

    • isFullWidth(str) : 檢查字符串是否包含全角字符

    • isHalfWidth(str) : 檢查字符串是否包含半角字符

    • isHexColor(str) : 檢查字符串是否是十六進(jìn)制的顏色

    • isHexadecimal(str) : 檢查字符串是否是十六進(jìn)制數(shù)

    • isIP(str [, version]) : 檢查字符串是否是一個(gè)IP(版本 4 , 6)

    • isISBN(str [, version]) : 檢查字符串是否是一個(gè)ISBN(版本10 ,13),ISBN是國(guó)際標(biāo)準(zhǔn)書號(hào),老版本10位,新版本13位.

    • isISIN(str) : 檢查字符串是否是ISIN , ISIN是國(guó)際證券識(shí)別編碼

    • isISO8601(str) : 檢查字符串是否是有效的ISO8601日期

    • isIn(str, values) : 檢查字符串是否在允許的值

    • isInt(str [, options]) : 檢查字符串是否是整數(shù),options是個(gè)對(duì)象,包含最大值,最小值,比如{min:0,max:100}

    • isJSON(str) : 檢查字符串是否是有效的json格式

    • isLength(str, options) : 檢查字符串長(zhǎng)度是否在范圍內(nèi),options是個(gè)對(duì)象,包含最大值,最小值,比如{min:0,max:100}

    • isLowercase(str) : 檢查字符串是否都是小寫字母.

    • isMACAddress(str) : 檢查字符串是否是MAC地址

    • isMobilePhone(str, locale) : 檢查字符串是否是手機(jī)號(hào),地區(qū):['ar-DZ', 'ar-SY', 'cs-CZ', 'de- DE', 'da-DK', 'el-GR', 'en-AU',
      'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-CA', 'en-ZA', 'en-ZM', 'es-ES', 'fi-FI', 'fr-FR', 'hu-HU', 'ms-MY',
      'nb-NO', 'nn-NO', 'pl-PL', 'pt-PT', 'ru-RU', 'tr-TR', 'vi-VN', 'zh-CN', 'zh-TW']).

    • isMongoId(str) : 檢查字符串是否是有效的mongodb objectid

    • isMultibyte(str) : 檢查字符串是否包含一個(gè)或多個(gè)多字節(jié)字符

    • isNull(str) : 檢查字符串是否為空,(length為0)

    • isNumeric(str) : 檢查字符串是否只包含數(shù)字

    • isSurrogatePair(str) : 檢查字符串是否包含 emoji表情字符 (主要是手機(jī)端)

    • isURL(str [, options]) : 檢查字符串是否是個(gè)URL

    • isUUID(str [, version]) : 檢查字符串是否是UUID(版本3,4,5)

    • isUppercase(str) : 檢查字符串是否是大寫

    • isVariableWidth(str) : 檢查字符串是否包含全角和半角混合字符.

    • isWhitelisted(str, chars) 檢查str是否都出現(xiàn)在chars中

    • matches(str, pattern [, modifiers]) : 檢查字符串是否匹配,比如 : matches('foo', /foo/i)或 matches('foo', 'foo', 'i').

  • 方法介紹

blacklist(input, chars) : 刪除出現(xiàn)在黑名單中的字符

`var blacklist = validator.blacklist('abcdefga','a');`
`console.log('blacklist :', blacklist);`
結(jié)果 : bcdegf

whitelist(input, chars) : 和blacklist相反

escape(input) : 將 <, >, &, ', " 和 / 轉(zhuǎn)換為html字符

`var escape = validator.escape('< - > - & - /');`
`console.log('escape :', escape);`

結(jié)果 :  < - > - & - /
 

unescape(input) : 和 escape方法相反

`var unescape = validator.unescape('< - > - & - /');`
 `console.log('unescape :', unescape);`
結(jié)果 :  < - > - & - /
 

ltrim(input [, chars]) : 從左邊開(kāi)始刪除滿足chars中的字符,直到不滿足為止.

`var ltrim = validator.ltrim('abcadefgabc','abc');`
`console.log('ltrim :', ltrim);`
結(jié)果 : defgabc
 
rtrim(input [, chars]) : 和 ltrim類似,從右邊開(kāi)始\
**trim(input [, chars]) : 從左右兩邊同時(shí)刪除.**

toBoolean(input [, strict]) : 轉(zhuǎn)換為boolean類型

**toDate(input) : 轉(zhuǎn)換為日期類型**

toFloat(input)  : 轉(zhuǎn)換為浮點(diǎn)類型

**toInt(input [, radix])  : 轉(zhuǎn)換為int類型**

七坦刀、for/for in/forEach/for of

1愧沟、for循環(huán)

var arr = ['nick','freddy','mike','james'];
for(var i = 0, len=arr.length; i<len; i++){
    console.log(i + '.' + arr[i]);
}
輸出結(jié)果:
0.nick
1.freddy
2.mike
3.james

for循環(huán),通過(guò)累加數(shù)組索引鲤遥,來(lái)輸出數(shù)組中的值沐寺。(使用比較局限,一般只用于循環(huán)數(shù)組)

2盖奈、for in 循環(huán)

var arr = ['nick','freddy','mike','james'];
var userMsg = {
  nick: {
      name: 'nick',
      age: 18,
      sex: '男'  
  },
  freddy: {
      name: 'freddy',
      age: 24,
      sex: '男'
  } 
};
 
for(var index in arr){  
    console.log(index +'. ' + arr[index]);
}
console.log('-----------分割線-----------');
for(var key1 in userMsg){
  console.log(key1);    
  for(var key2 in userMsg[key1]){
      console.log(userMsg[key1][key2]);
  }
}
輸出結(jié)果:
0. nick
1. freddy
2. mike
3. james
-----------分割線-----------
nick
nick
18
男
freddy
freddy
24
男

相較于for循環(huán)混坞,for in的功能會(huì)更加強(qiáng)大一些,使用范圍也會(huì)更廣钢坦,不但可以循環(huán)遍歷數(shù)組究孕,還可以循環(huán)遍歷對(duì)象。代碼中的index爹凹,key1厨诸,key2分別是目標(biāo)對(duì)象(數(shù)組)中的鍵值(數(shù)組中叫習(xí)慣叫索引)。arr數(shù)組中的index分別0,1,2,3禾酱,userMsg對(duì)象下的key1分別是"nick"微酬、"freddy"的鍵值, key2就是userMsg.nick和userMsg.freddy下的鍵值了绘趋,為"name"、"age"得封、"sex"。(for in在寫法上會(huì)稍微復(fù)雜些指郁,不過(guò)他很清晰的展示了循環(huán)過(guò)程)忙上。

3、forEach()循環(huán)

var arr = ['nick','freddy','mike','james'];
arr.forEach(function(item,index,arr){
  console.log(item);
  console.log(index);
  console.log(arr);
});
輸出結(jié)果:
nick
0
[ 'nick', 'freddy', 'mike', 'james' ]
freddy
1
[ 'nick', 'freddy', 'mike', 'james' ]
mike
2
[ 'nick', 'freddy', 'mike', 'james' ]
james
3
[ 'nick', 'freddy', 'mike', 'james' ]

forEach循環(huán)闲坎,跟for循環(huán)有點(diǎn)相似疫粥,不過(guò)會(huì)更優(yōu)美,可通過(guò)參數(shù)直接獲取到值腰懂,arr.forEach(function(item,index,arr){})梗逮,其中item為該索引下的值,index為索引绣溜,arr為數(shù)字本身慷彤,參數(shù)名可改變,但是順序不能改變怖喻。

4底哗、for of 循環(huán)

var arr = ['nick','freddy','mike','james'];
for(var item of arr){   
  console.log(item);
}
輸出結(jié)果:
nick
freddy
mike
james

遍歷數(shù)組里的每一項(xiàng)。

5锚沸、for of 與 for in 的區(qū)別

  • 區(qū)別①:for of無(wú)法循環(huán)遍歷對(duì)象

  • 區(qū)別②:遍歷輸出結(jié)果不同

for in循環(huán)遍歷的是數(shù)組的鍵值(索引)跋选,而for of循環(huán)遍歷的是數(shù)組的值。

  • 區(qū)別③:for in 會(huì)遍歷自定義屬性哗蜈,for of不會(huì)
var arr = ['nick','freddy','mike','james'];
arr.name = "數(shù)組";
 
for(var key in arr){
    console.log(key+': '+arr[key]); 
}
console.log('-----------分割線-----------');
for(var item of arr){   
    console.log(item);
}
輸出結(jié)果:
0: nick
1: freddy
2: mike
3: james
name: 數(shù)組
-----------分割線-----------
nick
freddy
mike
james

給數(shù)組添加一個(gè)自定義屬性name前标,并且賦值"數(shù)組"。然后進(jìn)行遍歷輸出的距潘,會(huì)發(fā)現(xiàn)新定義的屬性也被for in輸出來(lái)了炼列,而for of并不會(huì)對(duì)name進(jìn)行輸出。

八音比、git

1唯鸭、用戶名和郵箱設(shè)置

$ git config --global user.name "輸入用戶名"     (自定義)

$ git config --global user.email "輸入email"     (自定義)

2、git提交代碼

  • 創(chuàng)建遠(yuǎn)程倉(cāng)庫(kù)(GitHub,Gitee,coding...)

  • 如果沒(méi)有本地倉(cāng)庫(kù)

    echo "# toutiao-publish-admin" >> README.md

  • 初始化本地倉(cāng)庫(kù)

    git init

  • 把文件添加到暫存區(qū)

    git add README.md

  • 把暫存區(qū)文件提交到本地倉(cāng)庫(kù)形成歷史記錄

    git commit -m "first commit"

  • 添加遠(yuǎn)程倉(cāng)庫(kù)地址到本地倉(cāng)庫(kù)

    git remote add origin https://github.com/hyjAdmin/toutiao-publish-admin.git

  • 推送到遠(yuǎn)程倉(cāng)庫(kù)

    git push -u origin master

  • 如果已有本地倉(cāng)庫(kù)

    • VueCli 在創(chuàng)建項(xiàng)目的時(shí)候自動(dòng)幫我們初始化了Git倉(cāng)庫(kù)硅确,并且基于初始化代碼默認(rèn)執(zhí)行了一次提交

      git remote add origin https://github.com/hyjAdmin/toutiao-publish-admin.git

    • -u 記住本次推送的信息目溉,下次就不用寫推送信息了,可以直接 git push
      git push -u origin master

  • 之后如果代碼有變動(dòng)需要提交

    git add
    git commit
    
    • 推送到遠(yuǎn)程倉(cāng)庫(kù)

      git push

  • 項(xiàng)目修改 git 遠(yuǎn)程倉(cāng)庫(kù)地址

    (1)查看所有遠(yuǎn)程倉(cāng)庫(kù),一般默認(rèn)遠(yuǎn)程倉(cāng)庫(kù)名為origin
       git remote
    (2)修改當(dāng)前項(xiàng)目遠(yuǎn)程地址為 http://192.168.1.88:9090/test/git_test.git
       git remote set-url origin http://192.168.1.88:9090/test/git_test.git
    (3)更改地址后菱农,需要提交初始代碼到遠(yuǎn)程庫(kù)
       git push
    

3缭付、git創(chuàng)建和合并分支命令

3.1 拉取項(xiàng)目

首先,進(jìn)入到想要拉取分支的 項(xiàng)目 中 (首頁(yè)為 master 分支)

點(diǎn)擊上圖中標(biāo)注出來(lái)的 復(fù)制 (復(fù)制 URL 到剪貼板)按鈕復(fù)制路徑

打開(kāi)本地終端循未,進(jìn)入到想把項(xiàng)目存放的 目錄陷猫,git clone xxx ( xxx 為剛剛復(fù)制的 URL)

3.2 切換分支

使用 git clone xxx 命令后秫舌,系統(tǒng)會(huì)自動(dòng)創(chuàng)建項(xiàng)目文件夾

進(jìn)入 項(xiàng)目文件夾目錄,此時(shí)所處分支為 master分支

git checkout -b 分支名 新建分支(分支名為 想要拉取的 指定分支的 分支名)绣檬,然后此時(shí)系統(tǒng)會(huì)自動(dòng)切換為新建的這個(gè)分支

git pull 拉取分支足陨,更新分支內(nèi)容

此時(shí)會(huì)提示讓關(guān)聯(lián)分支,按照提示內(nèi)容輸入命令
git branch --set-upstream-to=origin/新建的那個(gè)分支名

重新 git pull

此時(shí)指定分支上的內(nèi)容就獲取完畢娇未。

3.3 git 命令

  • git checkout -b 分支名:新建并切換到新分支墨缘。
  • git branch: 查看當(dāng)前分支。
  • git checkout 分支名:切換分支(已有分支)零抬。
  • git branch -d 分支名:刪除分支镊讼。
  • git pull:拉取分支最新內(nèi)容。
  • git merge develop:將本分支內(nèi)容合并到 develop 分支上平夜。
  • git reset --hard origin/當(dāng)前分支名:將當(dāng)前分支本地編輯的所有內(nèi)容舍棄蝶棋。\

1)提交的時(shí)候:

查看分支:git branch

創(chuàng)建分支:git branch <name>

切換分支:git checkout <name>

創(chuàng)建+切換分支:git checkout -b <name>

合并某分支到當(dāng)前分支:git merge <分支名>     合并分支時(shí),加上--no-ff參數(shù)就可以用普通模式合并忽妒,合并后的歷史有分支玩裙,能看出來(lái)曾經(jīng)做過(guò)合并,而不加--no-ff合并就看不出來(lái)曾經(jīng)做過(guò)合并段直。例git merge --no-ff -m "詳細(xì)解釋" 分支

刪除分支:git branch -d <name>

查看分支合并圖: git log --graph

4献酗、gitlib配置SSH Key

在繼續(xù)閱讀后續(xù)內(nèi)容前,請(qǐng)自行注冊(cè)GitLab賬號(hào)(一般進(jìn)公司坷牛,配置管理員或者組長(zhǎng)會(huì)給你創(chuàng)建賬戶的)罕偎。由于你的本地Git倉(cāng)庫(kù)和GitLab倉(cāng)庫(kù)之間的傳輸是通過(guò)SSH加密的,所以京闰,需要以下設(shè)置:

4.1 第1步:

創(chuàng)建SSH Key颜及。在用戶主目錄下,看看有沒(méi)有.ssh目錄蹂楣,如果有俏站,再看看這個(gè)目錄下有沒(méi)有id_rsa和id_rsa.pub這兩個(gè)文件,如果已經(jīng)有了痊土,可直接跳到下一步肄扎。如果沒(méi)有,打開(kāi)Shell(Windows下打開(kāi)Git Bash)赁酝,創(chuàng)建SSH Key:

$ ssh-keygen -t rsa -C "youremail@example.com"

[圖片上傳失敗...(image-e327a0-1648865490772)]

你需要把郵件地址換成你自己的郵件地址犯祠,然后一路回車,使用默認(rèn)值即可酌呆,由于這個(gè)Key也不是用于軍事目的衡载,所以也無(wú)需設(shè)置密碼。
如果一切順利的話隙袁,可以在用戶主目錄里找到.ssh目錄痰娱,里面有id_rsa和id_rsa.pub兩個(gè)文件弃榨,這兩個(gè)就是SSH Key的秘鑰對(duì),id_rsa是私鑰梨睁,不能泄露出去鲸睛,id_rsa.pub是公鑰,可以放心地告訴任何人坡贺。

4.2 第2步:

登陸GitLab官辈,打開(kāi)“settings”,“SSH Keys”頁(yè)面:
然后拴念,點(diǎn)“Add SSH Key”钧萍,填上任意Title褐缠,在Key文本框里粘貼id_rsa.pub文件的內(nèi)容:

[圖片上傳失敗...(image-9b5ed1-1648865490772)]

點(diǎn)“Add Key”政鼠,你就應(yīng)該看到已經(jīng)添加的Key:

[圖片上傳失敗...(image-804677-1648865490772)]

為什么GitLab需要SSH Key呢?因?yàn)镚itLab需要識(shí)別出你推送的提交確實(shí)是你推送的队魏,而不是別人冒充的公般,而Git支持SSH協(xié)議,所以胡桨,GitLab只要知道了你的公鑰官帘,就可以確認(rèn)只有你自己才能推送。
當(dāng)然昧谊,GitLab允許你添加多個(gè)Key刽虹。假定你有若干電腦,你一會(huì)兒在公司提交呢诬,一會(huì)兒在家里提交涌哲,只要把每臺(tái)電腦的Key都添加到GitLab,就可以在每臺(tái)電腦上往GitLab推送了尚镰。
其他的操作就和GitHub是一樣的了.

5阀圾、gitlib如何創(chuàng)建分支和拉取代碼

從gitlab地址進(jìn)入進(jìn)行操作

5.1 登錄GitLab

賬號(hào)密碼由company統(tǒng)一提供,如果是自己操作的話狗唉,就需要先注冊(cè)

[圖片上傳失敗...(image-af758-1648865490772)]

5.2 登錄gitlab后對(duì)已創(chuàng)建好的項(xiàng)目進(jìn)行分支創(chuàng)建

此處默認(rèn)項(xiàng)目已創(chuàng)建好初烘,如果為創(chuàng)建項(xiàng)目可點(diǎn)擊右上角create project

  • 創(chuàng)建項(xiàng)目:

[圖片上傳失敗...(image-7be89e-1648865490772)]

  • 點(diǎn)擊進(jìn)入:

[圖片上傳失敗...(image-47f794-1648865490772)]

  • 在master分支創(chuàng)建自己提交代碼的分支,我命名為ddtm

[圖片上傳失敗...(image-f54d2f-1648865490772)]

5.3 創(chuàng)建后進(jìn)入sourcetree(默認(rèn)已安裝,未安裝的需要安裝好)

1)操作檢出代碼

2)需要查看他人代碼時(shí)需要合并master至想要的分支(ddtm)

[圖片上傳失敗...(image-2cf0c3-1648865490772)]

3)則可以查看到結(jié)果

[圖片上傳失敗...(image-fd47dc-1648865490772)]

5.4 這樣在master上創(chuàng)建分支拉取代碼的任務(wù)就完成了分俯。

一般情況在sourcetree上的也可以在master創(chuàng)建分支肾筐,但是我試過(guò)容易出問(wèn)題,所以最好的辦法就是在gitlab上使用賬號(hào)登錄的方式進(jìn)行創(chuàng)建最好

6缸剪、gitlab修改遠(yuǎn)程分支名稱

遠(yuǎn)程分支重命名 (已經(jīng)推送遠(yuǎn)程-假設(shè)本地分支和遠(yuǎn)程對(duì)應(yīng)分支名稱相同)

  • 重命名遠(yuǎn)程分支對(duì)應(yīng)的本地分支
git branch -m oldName newName
  • 刪除遠(yuǎn)程分支
git push --delete origin oldName
  • 上傳新命名的本地分支
git push origin newName
  • 把修改后的本地分支與遠(yuǎn)程分支關(guān)聯(lián)
git branch --set-upstream-to origin/newName

7局齿、git rebase 和 git merge

7.1 merge 和 rebase

  • merge 是一個(gè)合并操作,會(huì)將兩個(gè)分支的修改合并在一起橄登,默認(rèn)操作的情況下會(huì)提交合并中修改的內(nèi)容

  • merge 的提交歷史記錄了實(shí)際發(fā)生過(guò)什么抓歼,關(guān)注點(diǎn)在真實(shí)的提交歷史上面

  • rebase 并沒(méi)有進(jìn)行合并操作讥此,只是提取了當(dāng)前分支的修改,將其復(fù)制在了目標(biāo)分支的最新提交后面

  • rebase 操作會(huì)丟棄當(dāng)前分支已提交的 commit谣妻,故不要在已經(jīng) push 到遠(yuǎn)程萄喳,和其他人正在協(xié)作開(kāi)發(fā)的分支上執(zhí)行 rebase 操作

  • merge 與 rebase 都是很好的分支合并命令,沒(méi)有好壞之分蹋半,使用哪一個(gè)應(yīng)由團(tuán)隊(duì)的實(shí)際開(kāi)發(fā)需求及場(chǎng)景決定

7.2 git中多次commit合并成一個(gè)commit

  • 看一下當(dāng)前分支的提交情況
git log
  • 進(jìn)入編輯頁(yè)面
git rebase -i HEAD~3
  • 進(jìn)入編輯模式他巨,第一列為操作指令,第二列為commit號(hào)减江,第三列為commit信息染突。

    • pick:保留該commit;
    • reword:保留該commit但是修改commit信息辈灼;
    • edit:保留該commit但是要修改commit內(nèi)容份企;
    • squash:將該commit和前一個(gè)commit合并;
    • fixup:將該commit和前一個(gè)commit合并巡莹,并不保留該commit的commit信息司志;
    • exec:執(zhí)行shell命令;
    • drop:刪除該commit降宅。
  • 保存退出(:wq)

8骂远、git commit vim 編輯器

8.1 進(jìn)入編輯模式:(git commit -m 'your message'

  • 小寫i:在光標(biāo)所在行位置停止不動(dòng)開(kāi)始寫入內(nèi)容
  • 大寫I:在光標(biāo)所在行行首開(kāi)始寫入內(nèi)容
  • 小寫a:在光標(biāo)所在行當(dāng)前字符后開(kāi)始寫入內(nèi)容
  • 大寫A:在光標(biāo)所在行行尾開(kāi)始寫入內(nèi)容
  • 小寫o:在光標(biāo)所在行下一行開(kāi)始寫入內(nèi)容
  • 大寫O:在光標(biāo)所在行上一行開(kāi)始寫入內(nèi)容

8.2 退出編輯模式:(按下 “ESC” 鍵,退出編輯模式腰根,切換到命令模式

  • :w:保存文本
  • :q:退出編輯模式
  • :w!:強(qiáng)制保存激才,在root用戶下即使文本只讀也可以強(qiáng)制保存
  • :q!:強(qiáng)制退出,所有改動(dòng)不生效
  • :wq:保存并退出
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末额嘿,一起剝皮案震驚了整個(gè)濱河市瘸恼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岩睁,老刑警劉巖钞脂,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捕儒,居然都是意外死亡冰啃,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門刘莹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)阎毅,“玉大人,你說(shuō)我怎么就攤上這事点弯∩鹊鳎” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抢肛,是天一觀的道長(zhǎng)狼钮。 經(jīng)常有香客問(wèn)我碳柱,道長(zhǎng),這世上最難降的妖魔是什么熬芜? 我笑而不...
    開(kāi)封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任莲镣,我火速辦了婚禮,結(jié)果婚禮上涎拉,老公的妹妹穿的比我還像新娘瑞侮。我一直安慰自己,他們只是感情好鼓拧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布半火。 她就那樣靜靜地躺著,像睡著了一般季俩。 火紅的嫁衣襯著肌膚如雪钮糖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天种玛,我揣著相機(jī)與錄音藐鹤,去河邊找鬼瓤檐。 笑死赂韵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挠蛉。 我是一名探鬼主播祭示,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谴古!你這毒婦竟也來(lái)了质涛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掰担,失蹤者是張志新(化名)和其女友劉穎汇陆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體带饱,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毡代,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勺疼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片教寂。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖执庐,靈堂內(nèi)的尸體忽然破棺而出酪耕,到底是詐尸還是另有隱情,我是刑警寧澤轨淌,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布迂烁,位于F島的核電站看尼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盟步。R本人自食惡果不足惜狡忙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望址芯。 院中可真熱鬧灾茁,春花似錦、人聲如沸谷炸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旬陡。三九已至拓颓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間描孟,已是汗流浹背驶睦。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匿醒,地道東北人场航。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像廉羔,于是被迫代替她去往敵國(guó)和親溉痢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容