轉(zhuǎn)載:區(qū)塊鏈開(kāi)源實(shí)現(xiàn) hyperledger fabric 架構(gòu)詳解 (http://t.cn/R14zaCC) by 陶輝

Hyperledger Fabric是區(qū)塊鏈中聯(lián)盟鏈的優(yōu)秀實(shí)現(xiàn),主要代碼由IBM、Intel、各大銀行等貢獻(xiàn),目前v1.1版的kafka共識(shí)方式可達(dá)到1000/s次的吞吐量。本文中我們依次討論:區(qū)塊鏈的共通特性、fabric核心概念、fabric的交易執(zhí)行流程。本文來(lái)源于筆者欲對(duì)公司部分業(yè)務(wù)上鏈而進(jìn)行培訓(xùn)的PPT,故圖多文字少,不要怕太長(zhǎng)。
1、區(qū)塊鏈解決方案的特性
1.1 分布式帳本
區(qū)塊鏈核心概念是分布式帳本,就像下面的圖1所示,同樣的帳本(全量的交易數(shù)據(jù),詳見(jiàn)下節(jié))在任意一臺(tái)節(jié)點(diǎn)(不包括客戶端)上都有。所以,其優(yōu)點(diǎn)是數(shù)據(jù)很難造假,造假后也可以通過(guò)追溯記錄來(lái)追究法律責(zé)任。而缺點(diǎn)就是極大的浪費(fèi),傳統(tǒng)服務(wù)每份數(shù)據(jù)都盡量少存幾份,即使存了三份拷貝都已經(jīng)考慮到諸多異常,并使服務(wù)可用性達(dá)到N個(gè)9了。而區(qū)塊鏈這種特性,同時(shí)造成的另一個(gè)問(wèn)題是帳本不能太大,至少不能超過(guò)區(qū)塊鏈網(wǎng)絡(luò)中最小結(jié)點(diǎn)的存儲(chǔ)以及處理能力。所以,這制約了總交易數(shù)據(jù)(下文為方便概念介紹,統(tǒng)稱為帳本ledger)的條數(shù),進(jìn)而也影響了能寫(xiě)入?yún)^(qū)塊鏈的單條交易數(shù)據(jù)的大小。

圖1 區(qū)塊鏈分布式帳本示意圖
什么是區(qū)塊鏈呢?我很喜歡《區(qū)塊鏈技術(shù)進(jìn)階與實(shí)戰(zhàn)》一書(shū)中對(duì)它的定義:區(qū)塊鏈?zhǔn)且环N按照時(shí)間順序?qū)?shù)據(jù)區(qū)塊以順序相連的方式組合成的一種鏈?zhǔn)綌?shù)據(jù)結(jié)構(gòu)。如果覺(jué)得有點(diǎn)抽象,那么我們?cè)賮?lái)看看下面的圖2。

圖2-區(qū)塊鏈數(shù)據(jù)結(jié)構(gòu)示意
圖2中就是賬本,它由多個(gè)區(qū)塊構(gòu)成了一個(gè)有時(shí)序的鏈表,而每個(gè)區(qū)塊里含有多條交易trasaction(縮寫(xiě)為tx)構(gòu)成的鏈表。圖2下方有一個(gè)WorldState世界狀態(tài),這其實(shí)是為了提升性能用的。比如,key1共交易了10000次,為了獲取它的當(dāng)前狀態(tài)值,需要正向執(zhí)行這10000次交易,這就得不償失了。如果這1萬(wàn)次交易里,每次新交易執(zhí)行完,都同步更新一個(gè)數(shù)據(jù)庫(kù)(在fabric里用的是levelDB),這樣查詢當(dāng)前狀態(tài)時(shí),只需要查詢?cè)摂?shù)據(jù)庫(kù)即可,如圖3所示。

圖3-fabric levelDB狀態(tài)數(shù)據(jù)庫(kù)
圖3中,區(qū)塊鏈帳本是在FileSystem文件系統(tǒng)中保存的,而Level DB存放世界狀態(tài)。
1.2 智能合約smart contract
區(qū)塊鏈的發(fā)展過(guò)程中,一般1.0時(shí)代就是數(shù)字貨幣時(shí)代,代表是比特幣,而2.0時(shí)代就是智能合約(現(xiàn)在是3.0時(shí)代,各種聯(lián)盟鏈即為代表)。
智能合約是運(yùn)行在區(qū)塊鏈上的模塊化、可重用的自動(dòng)執(zhí)行腳本,有了它我們就可以完成復(fù)雜的業(yè)務(wù)邏輯,例如同一個(gè)區(qū)塊鏈上有多份合約,而每份合約可以約定不同的參與者(企業(yè)或者相關(guān)方)。也可以指定每份合約里每個(gè)子命令做一批特定的事,大家可以把它想象成關(guān)系數(shù)據(jù)庫(kù)里的事務(wù)。如圖4所示,我們可以在合約里指定允許哪些企業(yè)的節(jié)點(diǎn)可以參與到交易流程中來(lái)(在fabric里這叫共識(shí)策略)。

圖4-智能合約圖示
在fabric中,智能合約叫做chaincode,它有6個(gè)狀態(tài),如下所示:
- Install → Instantiate → invocable → Upgrade → Deinstantiate → Uninstall.
實(shí)際上智能合約就是一段代碼,fabric官方認(rèn)可的是GO語(yǔ)言。首先我們需要把合約代碼上傳到區(qū)塊鏈上,這一步的狀態(tài)就叫Install。
接著,需要做初始化操作。比如,現(xiàn)在的數(shù)據(jù)是存放在mysql中的,那么上線時(shí)需要用Instantiate把數(shù)據(jù)遷移至鏈上,這也算初始化。初始化后,chaincode就進(jìn)入invocable可調(diào)用狀態(tài)了。
通用我們可以通過(guò)CLI命令行或者程序里用SDK調(diào)用合約(v1.1前還有RestApi調(diào)用,現(xiàn)已放棄)。
聯(lián)盟鏈由于跨多家企業(yè)、多個(gè)地區(qū)甚至國(guó)家,很難使得合約保持一致的版本,因此,每個(gè)合約都有版本號(hào)。而版本升級(jí)時(shí),就是Upgrade狀態(tài)。
最后兩個(gè)狀態(tài)對(duì)應(yīng)著合約下鏈。
智能合約可以在供應(yīng)鏈等較復(fù)雜的業(yè)務(wù)場(chǎng)景下起到很大的作用,如下面的圖5所示:

圖5-智能合約技術(shù)的應(yīng)用示意
1.3 數(shù)據(jù)一致性(共識(shí)算法)
既然區(qū)塊鏈?zhǔn)且粋€(gè)去中心化的分布式系統(tǒng),那么自然只能通過(guò)投票來(lái)決定一致性了:少數(shù)服從多數(shù)。當(dāng)然,多少算多數(shù)呢?不同的共識(shí)算法下,結(jié)果并不相同。比如paxos算法(參見(jiàn)筆者的《paxos算法如何容錯(cuò)的–講述五虎將的實(shí)踐》)就是超過(guò)一半,而PBFT則需要三分之二以上。
這里有一個(gè)拜占庭將軍問(wèn)題需要注意,如何理解該問(wèn)題可以參見(jiàn)這份翻譯過(guò)的The_Part-Time_Parliament(Paxos算法中文翻譯)文檔。簡(jiǎn)言之,就是投票的拜占庭將軍(服務(wù)器)們有2種不可靠的形式。第一是遲鈍(數(shù)據(jù)包延遲)、失憶(數(shù)據(jù)包丟失以及數(shù)據(jù)包重發(fā))、失蹤(服務(wù)器宕機(jī))等不含背叛的行為,第二則是有將軍是間諜(服務(wù)器被攻破)。如paxos這樣的算法屬于第一種,F(xiàn)ault-tolerance,它不能容忍服務(wù)器上有惡意代碼;而如PBFT(Practical Byzantine Fault Tolerance)這樣的算法是第二類,Byzantine-Fault-tolerance,它能夠容忍一定數(shù)量的拜占庭將軍節(jié)點(diǎn)存在,如PBFT、SBFT、RBFT算法等。
第二類Byzantine-Fault-tolerance共識(shí)算法雖然看上去很美,但并不成熟,特別是性能低下,比如PBFT是一個(gè)多項(xiàng)式復(fù)雜度的算法O(N^2),節(jié)點(diǎn)過(guò)多時(shí)(大于100)性能急驟下降。第一類通常是O(N)復(fù)雜度,在某些場(chǎng)景下使用效果還不錯(cuò),比如fabric v1.1的kafka共識(shí)機(jī)制就是這樣的算法,下文我們會(huì)詳述。
像比特幣、以太坊等采用的共識(shí)算法又有所不同,例如比特幣的POW工作量證明算法,它定義一小時(shí)內(nèi)(通過(guò)調(diào)整運(yùn)算難度實(shí)現(xiàn),比如調(diào)整近似程度)有一個(gè)lucky node節(jié)點(diǎn),該節(jié)點(diǎn)是通過(guò)證明自身的努力(hash值逆解)而幸運(yùn)選出,選出后它就可以為這段時(shí)間的交易做決定(似乎挺像總統(tǒng)選舉_)。詳情參見(jiàn)我這篇文章:《區(qū)塊鏈技術(shù)學(xué)習(xí)筆記》
1.4 非對(duì)稱加密
區(qū)塊鏈通過(guò)非對(duì)稱加密技術(shù)實(shí)現(xiàn)身份驗(yàn)證與數(shù)據(jù)加密。其實(shí)就是我們?nèi)粘T谟玫腟SL技術(shù)。
為了方便理解,我們需要先介紹PKI(Public Key Infrastructure),它是一種遵循標(biāo)準(zhǔn)的利用公鑰加密技術(shù)為電子商務(wù)的開(kāi)展提供一套安全基礎(chǔ)平臺(tái)的技術(shù)和規(guī)范。有一個(gè)CA(Certificate Authority)權(quán)威機(jī)構(gòu)負(fù)責(zé)向用戶(包括服務(wù)提供者與使用者)提供數(shù)字證書(shū),包括公鑰與私鑰,同時(shí)CA機(jī)構(gòu)還需要提供一個(gè)CRL(Certificate Revocation List)證書(shū)吊銷列表,如下面的圖6所示。

圖6-CA機(jī)構(gòu)頒發(fā)數(shù)字證書(shū)以及提供CAL
這樣,區(qū)塊鏈可以通過(guò)PKI體系實(shí)現(xiàn)安全認(rèn)證。PKI有三個(gè)關(guān)鍵點(diǎn),我們下面詳述。
1.4.1 數(shù)字證書(shū) Digital Certificate
比如Mary Morris符合X.509規(guī)范的數(shù)字證書(shū)里,其Subject屬性里就含有她的信息,包括國(guó)家C=US、所屬的州或者省份ST=Michigan、所在城市L=Detroit、所屬單位O=Mitchesll Cars、其他信息OU=Manufacturing、公用信息CN=Mary Morris/UID=123456等,也含有其他信息,如下面的圖7所示。

圖7-PKI數(shù)字證書(shū)
1.4.2 公鑰與私鑰
CA頒發(fā)了兩個(gè)證書(shū):公鑰與私鑰,其中,私鑰僅服務(wù)提供者保存,而公鑰則可被所有人(服務(wù)使用者)保存。
所謂非對(duì)稱加密,就是公鑰加密的消息僅私鑰可以解密;同理,私鑰加密的消息,僅公鑰可以解密。對(duì)應(yīng)于前者,可以實(shí)現(xiàn)客戶端訪問(wèn)服務(wù)器時(shí)加密消息,例如訪問(wèn)安全級(jí)別高的頁(yè)面時(shí)提交的表單信息都需要用公鑰加密,確保只有服務(wù)器才能解密網(wǎng)絡(luò)報(bào)文。對(duì)應(yīng)于后者,則可實(shí)現(xiàn)簽名功能,如下面的圖8所示。

圖8-PKI中私鑰簽名后用公鑰驗(yàn)簽名
圖8中Mary Morris用私鑰對(duì)一段信息的內(nèi)容(若內(nèi)容過(guò)大則可先HASH后獲得小點(diǎn)的字符串)加密后,生成簽名附加在消息中。接收者可從CA機(jī)構(gòu)獲取到公鑰,用公鑰解密簽名后,再與內(nèi)容比對(duì),以確定消息是否來(lái)自MaryMorris及內(nèi)容是否被篡改。對(duì)于文件來(lái)說(shuō)也是一樣,小文件直接加密,大文件先生成hash再對(duì)hash加密,如下面的圖9所示。

圖9-對(duì)文件的簽名
1.4.3 證書(shū)信任鏈
CA證書(shū)分為兩類:RCA(Root CA)根證書(shū)以及ICA(Intermediate CA)中間證書(shū)。這些證書(shū)由RCA開(kāi)始構(gòu)成一個(gè)證書(shū)信任鏈,如下面的圖10所示。

圖10-CA證書(shū)信任鏈條
有許多CA證書(shū)權(quán)威機(jī)構(gòu),各自有其RCA。如果RCA得不到信任,那么其下的ICA也無(wú)法認(rèn)證通過(guò)。
當(dāng)然,自己的服務(wù)器也可以生成RCA。
在Fabric里,允許不同的企業(yè)使用不同的RCA,也可以使用相同的RCA和不同的ICA。這與下文中的MSP密切相關(guān)。
1.5 小結(jié)
我們來(lái)總結(jié)下區(qū)塊鏈,它主要是為了解決社會(huì)上的信任問(wèn)題而存在的,為此,它付出了沉重的性能、可用性代價(jià)。它怎么做到的呢?通過(guò)4點(diǎn)實(shí)現(xiàn):1、數(shù)據(jù)到處存放;2、操作記錄不可更改;3、傳輸數(shù)據(jù)可信;4、業(yè)務(wù)腳本約束。
那么,這個(gè)信任問(wèn)題的解決,帶來(lái)了2個(gè)非功能性的約束:數(shù)據(jù)一致性和可用性。其中可用性包括兩點(diǎn):1、交易在可接受的時(shí)間內(nèi)達(dá)成。比如比特幣的分叉就會(huì)造成嚴(yán)重問(wèn)題。2、吞吐量達(dá)標(biāo)。而比特幣每秒只能有7次交易,這顯然太低了。
2、fabric核心概念
hyperledger fabric符合上面說(shuō)過(guò)的區(qū)塊鏈的所有特性。我們必須先了解它的一些概念,才能進(jìn)一步理解其架構(gòu)設(shè)計(jì)。由于英文資料居多,所以這些概念我都以英文描述為準(zhǔn):
- chaincode:智能合約,上文已提到。每個(gè)chaincode可提供多個(gè)不同的調(diào)用命令。
- transaction:交易,每條指令都是一次交易。
- world state:對(duì)同一個(gè)key的多次交易形成的最終value,就是世界狀態(tài)。
- endorse:背書(shū)。金融上的意義為:指持票人為將票據(jù)權(quán)利轉(zhuǎn)讓給他人或者將一定的票據(jù)權(quán)利授予他人行使,而在票據(jù)背面或者粘單上記載有關(guān)事項(xiàng)并簽章的行為。通常我們引申為對(duì)某個(gè)事情負(fù)責(zé)。在我們的共識(shí)機(jī)制的投票環(huán)節(jié)里,背書(shū)意味著參與投票。
- endorsement policy:背書(shū)策略。由智能合約chaincode選擇哪些peer節(jié)點(diǎn)參與到背書(shū)環(huán)節(jié)來(lái)。
- peer:存放區(qū)塊鏈數(shù)據(jù)的結(jié)點(diǎn),同時(shí)還有endorse和commit功能。
- channel:私有的子網(wǎng)絡(luò),事實(shí)上是為了隔離不同的應(yīng)用,一個(gè)channel可含有一批chaincode。
- PKI:Public Key Infrastructure,一種遵循標(biāo)準(zhǔn)的利用公鑰加密技術(shù)為電子商務(wù)的開(kāi)展提供一套安全基礎(chǔ)平臺(tái)的技術(shù)和規(guī)范。
- MSP:Membership Service Provider,聯(lián)盟鏈成員的證書(shū)管理,它定義了哪些RCA以及ICA在鏈里是可信任的,包括定義了channel上的合作者。
- org:orginazation,管理一系列合作企業(yè)的組織。
2.1 開(kāi)發(fā)概念
fabric聯(lián)盟鏈的開(kāi)發(fā)人員主要分為三類:底層是系統(tǒng)運(yùn)維,負(fù)責(zé)系統(tǒng)的部署與維護(hù);其次是組織管理人員,負(fù)責(zé)證書(shū)、MSP權(quán)限管理、共識(shí)機(jī)制等;最后是業(yè)務(wù)開(kāi)發(fā)人員,他們負(fù)責(zé)編寫(xiě)chaincode、創(chuàng)建維護(hù)channel、執(zhí)行transaction交易等,如下面的圖11所示。

圖11-fabric技術(shù)人員的分層
fabric大致分為底層的網(wǎng)絡(luò)層、權(quán)限管理模塊、區(qū)塊鏈應(yīng)用模塊,通過(guò)SDK和CLI對(duì)應(yīng)用開(kāi)發(fā)者提供服務(wù),如下面的圖12所示。

圖12-fabric開(kāi)發(fā)模塊圖
我們的開(kāi)發(fā)流程主要包括寫(xiě)智能合約,以及通過(guò)SDK調(diào)用智能合約,及訂閱各類事件,如圖13所示。

圖13-開(kāi)發(fā)環(huán)節(jié)
2.2 MSP
每個(gè)管理協(xié)作企業(yè)的ORG組織都可以擁有自己的MSP。如下圖14所示,組織ORG1擁有的MSP叫ORG1.MSP,而組織ORG2業(yè)務(wù)復(fù)雜,所以維護(hù)了3個(gè)MSP。

圖14-ORG可管理自己的MSP
MSP出現(xiàn)在兩個(gè)地方:在channel上有一個(gè)全局的MSP,而每個(gè)peer、orderer、client等角色上都維護(hù)有本地的局部MSP,如圖15所示。

圖15-在channel上的Global MSP以及在參與角色上的Local MSP
本地MSP只保存有Global MSP上的子集,內(nèi)容保存在本地文件系統(tǒng)上,而全局MSP可在邏輯上認(rèn)為是配置在系統(tǒng)上的,它實(shí)際也在每個(gè)參與者上保存一份拷貝,但會(huì)維持一致性。
MSP也分級(jí),如圖16中所示,底層的network MSP負(fù)責(zé)網(wǎng)絡(luò)層的準(zhǔn)入,其MSP由ORG1擁有,而上面的某個(gè)channel的MSP則由ORG1和ORG2共同管理。

圖16-MSP是分級(jí)的
一個(gè)MSP下含有以下結(jié)構(gòu),如圖17所示。

圖17-MSP結(jié)構(gòu)
可見(jiàn),MSP結(jié)構(gòu)包括:
- RCA根證書(shū)
- ICA中間證書(shū)
- OU組織單位
- 管理員證書(shū)
- RCL吊銷證書(shū)列表
- 結(jié)點(diǎn)上的具體證書(shū)
- 存儲(chǔ)私鑰的keystore
- TLS的根證書(shū)與中間證書(shū)
3、fabric交易提交流程
3.1 peer結(jié)點(diǎn)的部署
peer結(jié)點(diǎn)上保存有賬本ledger以及智能合約,如下圖所示:

channel是一個(gè)邏輯概念,可以通過(guò)MSP隔離全網(wǎng)不同組織的參與者,如下圖所示:

當(dāng)有多方參與者時(shí),例如4個(gè)org組織、8個(gè)peer結(jié)點(diǎn)時(shí),其中channel連接了P1、P3、P5、P7、P8這五個(gè)節(jié)點(diǎn),其他3個(gè)節(jié)點(diǎn)加入了其他channel,其部署圖如下所示:

加入MSP來(lái)管理身份時(shí),如P1和P2由ORG1.MSP管理,而P3和P4的證書(shū)則由ORG2.MSP管理,他們共同使用一個(gè)channel,則如下圖所示:

3.2 交易的執(zhí)行流程
去中心化的設(shè)計(jì),必然需要通過(guò)投票(多數(shù)大于少數(shù))來(lái)維持?jǐn)?shù)據(jù)一致性,而任何投票都必須經(jīng)歷以下三個(gè)過(guò)程:
- 有一方先提出議案proposal,該議案有對(duì)應(yīng)的一批投票者需要對(duì)該結(jié)果背書(shū),這些投票者依據(jù)各自的習(xí)慣投票,并將結(jié)果反饋;
- 統(tǒng)計(jì)投票結(jié)果,若獲得多數(shù)同意,才能進(jìn)行下一步;
- 將獲得多數(shù)同意的議案記錄下來(lái),且公之于眾。
而這三步fabric當(dāng)然也少不了,當(dāng)然它的稱法就有所不同,其對(duì)應(yīng)的三步如下:
- 由client上的CLI或者SDK進(jìn)行proposal議案的提出。client會(huì)依據(jù)智能合約chaincode根據(jù)背書(shū)策略endorse policy決定把proposal發(fā)往哪些背書(shū)的peer節(jié)點(diǎn),而peer節(jié)點(diǎn)進(jìn)行投票,client匯總各背書(shū)節(jié)點(diǎn)的結(jié)果;
- client將獲得多數(shù)同意的議案連同各peer的背書(shū)(包括其投票結(jié)果以及背書(shū)簽名)交給orderring service,而orderer會(huì)匯總各client遞交過(guò)來(lái)的trasaction交易,排序、打包。
- orderer將交易打包成區(qū)塊block,然后通知所有commit peer,各peer各自驗(yàn)證結(jié)果,最后將區(qū)塊block記錄到自己的ledger賬本中。
我們看一個(gè)具體的例子,若channel上有三個(gè)peer背書(shū)者,client提交流程如下圖所示:

詳細(xì)解釋下上圖的流程:
- 首先,client發(fā)起一個(gè)transaction交易,含有<clientID, chaincodeID, txPayLoad, timestamp, clientSig>等信息,指明了3W要素:消息是誰(shuí)who在什么時(shí)間when發(fā)送了什么what。該消息根據(jù)chaincode中的背書(shū)策略,發(fā)向EP1、EP2、EP3這三個(gè)peer節(jié)點(diǎn)。
- 這三個(gè)peer節(jié)點(diǎn)模擬執(zhí)行智能合約,并將結(jié)果及其各自的CA證書(shū)簽名發(fā)還client。client收集到足夠數(shù)量的結(jié)果后再進(jìn)行下一步。
- client將含背書(shū)結(jié)果的tx交易發(fā)向ordering service。
- ordering service將打包好的block交給committing peer CP1以及EP1、EP2、EP3這三個(gè)背書(shū)者,背書(shū)者此時(shí)會(huì)校驗(yàn)結(jié)果并寫(xiě)入世界狀態(tài)以及賬本中。同時(shí),client由于訂閱了消息,也會(huì)收到通知。
如果我們從編程的角度來(lái)看,則流程會(huì)更清楚:

參見(jiàn)上圖,A是我們的應(yīng)用程序,其步驟如下:
- A首先連接到peer。
- A調(diào)用chaincode發(fā)起proposal;與此同時(shí),P1收到后先模擬執(zhí)行,再產(chǎn)生結(jié)果返回給A。
- A收到各peer返回的結(jié)果。
- A向O1發(fā)起交易;與此同時(shí),O1產(chǎn)生區(qū)塊后會(huì)通知peer,而peer會(huì)更新其賬本。
- 最后通過(guò)訂閱事件A收到了結(jié)果。
最后再細(xì)看下這三個(gè)階段。
3.2.1 proposal提案階段

可以看到,A1發(fā)出的<T1, P>,收到了<T1, R1, E1>和<T1, R2, E2>兩個(gè)結(jié)果。
3.2.2 package打包階段

O1在一個(gè)channel上會(huì)收到許多T交易,它會(huì)將T排序,在達(dá)到block的最大大?。ㄒ话銘?yīng)配1M以下,否則性能下降嚴(yán)重,kafka擅長(zhǎng)處理小點(diǎn)的消息)或者達(dá)到超時(shí)時(shí)間后,打成區(qū)塊P2。
3.2.3 驗(yàn)證階段

O1將含有多條交易T打成區(qū)塊的B2發(fā)往各peer節(jié)點(diǎn),而P1和P2將B2加入各自的L賬本中。
4、小結(jié)
本文偏重于概念的解釋,由于篇幅所限,未涉及fabric的系統(tǒng)搭建(請(qǐng)參考筆者的這篇文章《區(qū)塊鏈開(kāi)源實(shí)現(xiàn)fabric快速部署及CLI體驗(yàn)》),也未描述共識(shí)算法在異常情況下如何維持一致性,這留待下一篇文章解決。fabric的許多思想是值得我們進(jìn)一步研究的,其優(yōu)秀的實(shí)現(xiàn)可以幫助我們通過(guò)fabric獲得區(qū)塊鏈在信任創(chuàng)新上的思路。
(轉(zhuǎn)載本站文章請(qǐng)注明作者和出處 陶輝筆記 ,請(qǐng)勿用于任何商業(yè)用途)