一芒率、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
遗契,CMD
,FROM
病曾,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ì)被顯示在幫助信息里巧涧。這里的alias
和description
僅是錦上添花而已薯蝎。
更復(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.parallelLimit
與 async.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)目代碼中修改配置信息日矫。
- 使用步驟
- 使用
npm install config
命令下載模塊 - 在項(xiàng)目的根目錄下新建config文件夾
- 在config文件夾下新建default.json赂弓、development.json、production.json文件
- 在項(xiàng)目中通過(guò)require方法搬男,將模塊導(dǎo)入程序中
- 使用模塊內(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)變量中。操作步驟如下
- 在config文件夾中建立
custom-environment-variables.json
文件姓惑。 - 配置項(xiàng)屬性的值填寫系統(tǒng)環(huán)境變量的名字
- 項(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-USisAlphanumeric(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 add .
git commit -m "描述提交的內(nèi)容"
git push
- git 如何把master分支代碼合并到自己的分支
- 轉(zhuǎn)載自https://blog.csdn.net/Bule_daze/article/details/103272403
查看分支: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:保存并退出