Java中怎么設(shè)計(jì)應(yīng)用層網(wǎng)關(guān),很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
創(chuàng)新互聯(lián)從2013年創(chuàng)立,先為凌云等服務(wù)建站,凌云等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為凌云企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
Java 應(yīng)用層網(wǎng)關(guān)的必要性
我們的 Java 網(wǎng)關(guān)分為應(yīng)用層網(wǎng)關(guān)和業(yè)務(wù)嵌入式網(wǎng)關(guān)兩部分,架構(gòu)圖如下
在這里插入圖片描述
Java 網(wǎng)關(guān)分為核心網(wǎng)關(guān)和業(yè)務(wù)嵌入式網(wǎng)關(guān)服務(wù)兩部分,主要工作原理如下
接入層流量首先進(jìn)入 Java 核心網(wǎng)關(guān),經(jīng)過一系列的 pipeline 處理(風(fēng)控,路由協(xié)議轉(zhuǎn)換、流控、降級(jí)等操作)后發(fā)起泛化調(diào)用再打入業(yè)務(wù)層網(wǎng)關(guān)
業(yè)務(wù)層網(wǎng)關(guān)也會(huì)經(jīng)過一系列的 pipeline(接口校驗(yàn),驗(yàn)簽,session 校驗(yàn)等)進(jìn)入最終的業(yè)務(wù)邏輯,然后再調(diào)用相關(guān) dubbo 服務(wù)最終完成本次 Java 請(qǐng)求的響應(yīng)。
核心網(wǎng)關(guān)與嵌入式業(yè)務(wù)網(wǎng)關(guān)的功能如下
在這里插入圖片描述
其中嵌入式網(wǎng)關(guān)是以 jar 包的形式集成到業(yè)務(wù)的工程里的,具體為啥要這樣設(shè)計(jì),后文會(huì)詳述。
首先來看 Java 網(wǎng)關(guān)為啥要分成核心網(wǎng)關(guān)和嵌入式業(yè)務(wù)網(wǎng)關(guān)兩部分,直接從接入層打到業(yè)務(wù)網(wǎng)關(guān)不是更省事嗎,何必多此一舉再加一層核心網(wǎng)關(guān),多加一層不是多了一個(gè)損耗嗎。
這里有三個(gè)原因
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
核心網(wǎng)關(guān)主要起著風(fēng)控,鑒權(quán)、路由協(xié)議轉(zhuǎn)換、流控、降級(jí),打點(diǎn)統(tǒng)計(jì)(請(qǐng)求報(bào)錯(cuò)等)等作用,這些功能對(duì)每一個(gè)層請(qǐng)求來說都是通用的,統(tǒng)一將這些功能抽離放在核心網(wǎng)關(guān)實(shí)現(xiàn)更合理。
當(dāng)然了,可以統(tǒng)一把第一點(diǎn)所述的這些功能放在接入層實(shí)現(xiàn),但這樣會(huì)讓接入層顯得很臃腫,另外第一點(diǎn)中有一個(gè)很重要的功能,路由協(xié)議轉(zhuǎn)換(將 http 轉(zhuǎn)成 dubbo),由于我們的接入層用的是 OpenResty,它是不支持這種協(xié)議轉(zhuǎn)換的,除非基于 OpenResty 做二次開發(fā),這樣費(fèi)時(shí)費(fèi)力,也無必要,這樣看來抽出一個(gè) Java 核心網(wǎng)關(guān)來擔(dān)任第一點(diǎn)所述的功能是更合理的,計(jì)算機(jī)界不有一句話么:任何問題,在計(jì)算機(jī)界都可以通過加入一個(gè)中間層來解決。加一個(gè) Java 核心網(wǎng)關(guān)符合單一職責(zé),分層的設(shè)計(jì)理念。
加入一個(gè)核心網(wǎng)關(guān),確實(shí)多了一層,也多了一個(gè)損耗,不過核心網(wǎng)關(guān)并不處理具體的邏輯,它主要起著流量轉(zhuǎn)發(fā)的作用,而且在下文我們可以看到,它采用了 webflux 這種反應(yīng)式編程框架,帶來的損耗比起引入它帶來的優(yōu)勢(shì)可以忽略不計(jì)。
接下來我們簡(jiǎn)單談?wù)労诵木W(wǎng)關(guān)和業(yè)務(wù)網(wǎng)關(guān)的設(shè)計(jì)思路。
核心網(wǎng)關(guān)技術(shù)選型
同步阻塞 VS 異步非阻塞
上節(jié)介紹可知 Java 核心網(wǎng)關(guān)承擔(dān)著所有的流量入口,本身會(huì)調(diào)用大量的業(yè)務(wù)接口(打到業(yè)務(wù)網(wǎng)關(guān)里),所以 IO 操作會(huì)很頻繁,在技術(shù)選型上是有要求的, 首先來看看傳統(tǒng)的 Spring MVC(servlet 3.0之前)
很明顯它是同步阻塞的, 一個(gè)請(qǐng)求需要對(duì)應(yīng)一個(gè) Servlet Thread 來處理,當(dāng)有 DB,網(wǎng)絡(luò) IO 時(shí),此線程會(huì)阻塞,可想而知用這種方案線程很快會(huì)占滿,導(dǎo)致系統(tǒng)不可用。
顯然我們應(yīng)該采用異步非阻塞的編程模型,它是如何工作的呢,如下圖示
工作原理如下
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
只有一個(gè) request 線程負(fù)責(zé) accept 所有的請(qǐng)求,每個(gè)請(qǐng)求都有一個(gè) Event handler 和回調(diào),request 線程接收到 request 請(qǐng)求后,首先會(huì)為此請(qǐng)求在 Event Loop 中注冊(cè)一個(gè)回調(diào)函數(shù),緊接著馬上把這個(gè)請(qǐng)求丟給線程池中的某個(gè)線程處理,然后此 request 線程立馬返回,馬上就可以處理另外的請(qǐng)求了。
線程池中的線程處理完請(qǐng)求的 Event Handler(DB,網(wǎng)絡(luò)IO等邏輯) 后,會(huì)去調(diào)用之前注冊(cè)好的回調(diào)函數(shù)返回請(qǐng)求結(jié)果
從以上的工作原理可以看出,負(fù)責(zé)處理請(qǐng)求的 request 線程只需求一個(gè),線程數(shù)大大減少!更少的線程意味著更高的內(nèi)存利用,也意味著線程間的切換開銷大大減少!所以顯然應(yīng)該使用這種編程模型。
打個(gè)簡(jiǎn)單的比方,相信大家都有去酒店就餐的經(jīng)歷,對(duì)于酒店來說,怎么才能最大化地提高接客效率呢
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
一種方式是對(duì)每一個(gè)客人,都安排一位接待員,這名接待員負(fù)責(zé)客人的接待,入座,上菜等所有流程,顯然如果這樣安排的話有多少位客人就等安排多少位接待員。
第二種方式是只安排一位接待員,這名接待員在接待客人入座后,立刻回到門口迎接客人,剩下的交給上菜服務(wù)員(線程池工作),這樣的話接待員的人數(shù)就大大減少了,能極大地提升效率。
最終我們選擇了 Spring WebFlux 這種反應(yīng)式(Reactive),基于事件驅(qū)動(dòng)的異步非阻塞框架。
反應(yīng)式編程與 Spring WebFlux 簡(jiǎn)介
反應(yīng)式編程簡(jiǎn)介
反應(yīng)式編程 (reactive programming) 是一種基于數(shù)據(jù)流 (data stream) 和 變化傳遞 (propagation of change) 的 聲明式 (declarative) 的編程范式。它是一種編程思想,能夠基于數(shù)據(jù)流中的事件(變化)進(jìn)行相關(guān)反應(yīng)處理,舉個(gè)簡(jiǎn)單的例子:在 a = b + c 這個(gè)語句中,要得到 a 的值,如果用傳統(tǒng)的編程模型,每次 b 或 c 變化后都需要重新計(jì)算以獲得 a,而在反應(yīng)式編程中,我們把 b,c 當(dāng)作數(shù)據(jù)流,a 會(huì)對(duì) b,c 作出的變化實(shí)時(shí)響應(yīng)。
反應(yīng)式編程有以下幾個(gè)特點(diǎn)
1、事件驅(qū)動(dòng)
在事件驅(qū)動(dòng)的程序中,組件之間通過松藕合的生產(chǎn)者(也稱被訂閱者,即 Publisher)和訂閱者模式(Subscriber)來實(shí)現(xiàn),這些事件是以異步和非阻塞的方式來接收和發(fā)送的,基于事件驅(qū)動(dòng)的編程有啥好處呢,簡(jiǎn)單地說它是依靠推模式而不是拉模式來動(dòng)作的,也就是說只有生產(chǎn)者有消息(變化)時(shí)才會(huì)通知消費(fèi)者作出響應(yīng),也就意味著消費(fèi)者不需要輪詢也不需要等待數(shù)據(jù)。
2、實(shí)時(shí)響應(yīng)
以我們的網(wǎng)關(guān)為例, request 線程接收請(qǐng)求后,快速返回存儲(chǔ)結(jié)果的上下文,把具體執(zhí)行交給線程池里的線程(可以認(rèn)為是后臺(tái)線程),處理完成后,異步地將調(diào)用結(jié)果封裝到結(jié)果的上下文中,可以看到此過程是完全異步的,也就是說實(shí)時(shí)響應(yīng)必須通過異步編程實(shí)現(xiàn),在 Java 8 中,發(fā)起調(diào)用后可以快速返回 CompletableFuture 對(duì)象。
3、彈性機(jī)制
事件驅(qū)動(dòng)的松散耦合提供了組件在失敗下可以抓獲完全隔離的上下文場(chǎng)景,作為消息封裝,發(fā)送到其他組件時(shí),在具體編程時(shí)可以檢查錯(cuò)誤比如是否接受到,接受的命令是否可執(zhí)行等等,并決定如何應(yīng)對(duì)。
反應(yīng)式編程主要工作流程如下
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
被訂閱者主動(dòng)推送數(shù)據(jù)給訂閱者,在異步或完成時(shí)觸發(fā)另外的兩個(gè)方法
被訂閱者發(fā)生異常,會(huì)觸發(fā) onError
所有的推送完成無異常,最終會(huì)執(zhí)行 onSuccess 方法
還有一個(gè)問題,如果 Publisher 發(fā)送消息過快超過 Subscriber 的處理速度了怎么辦,所以就得提一下背壓(BackPressure)的概念了,知乎網(wǎng)友扔物線對(duì)此概念解釋我認(rèn)為非常到位:
backpressure 是源自工程學(xué)中的概念:在管道運(yùn)輸中,氣流或液流由于管道突然變細(xì)、急彎等原因?qū)е掠赡程幊霈F(xiàn)了下游向上游的逆向壓力,這種情況稱為「backpressure」,相應(yīng)的在反應(yīng)式編程中,在數(shù)據(jù)流從上游生產(chǎn)者向下游消費(fèi)者傳輸?shù)倪^程中,上游生產(chǎn)速度大于下游消費(fèi)速度,導(dǎo)致下游的 Buffer 溢出,這種現(xiàn)象就叫做 Backpressure 出現(xiàn),這里的重點(diǎn)在于「Buffer 溢出」,為什么需要 buffer, 因?yàn)?Publisher 生產(chǎn)速度大于 Subscriber 的消費(fèi)速度,所以需要 Buffer, 因?yàn)橥獠織l件限制,顯然 Buffer 是有上限的,如果生產(chǎn)速度超過 buffer, 則 backpressure 產(chǎn)生,超過 buffer 的話,唯一的選擇就是丟掉新事件。
這就好比,比如你的 server 只能承受 5000~6000 的請(qǐng)求,如果你把 buffer 設(shè)置為 5000,則一旦請(qǐng)求數(shù)超過 5000,則背壓產(chǎn)生,超過的請(qǐng)求數(shù)丟棄,這樣保證了機(jī)器不會(huì)被源源不斷的 Publisher 生產(chǎn)事件壓垮,有效提升了網(wǎng)關(guān)的可用性。
Spring WebFlux 簡(jiǎn)介
為了更好地促進(jìn)反應(yīng)式編程的應(yīng)用,在 Java 平臺(tái)上,Netflix(開發(fā)了 RxJava)、TypeSafe(開發(fā)了 Scala、Akka)、Pivatol(開發(fā)了 Spring、Reactor)共同制定了一個(gè)被稱為 Reactive Streams 項(xiàng)目(規(guī)范),用于制定反應(yīng)式編程相關(guān)的規(guī)范以及接口。
Reactor 基于 Reactive Stream 定制了一套反應(yīng)式編程框架,而 WebFlux 則是以 Reactor 為基礎(chǔ)實(shí)現(xiàn)了 Web 領(lǐng)域的反應(yīng)式編程框架,由于反應(yīng)式編程的異步非阻塞特性,所以 WebFlux 運(yùn)行于 Netty , Undertow 等支持異步編程模型的 server 之上,當(dāng)然也可運(yùn)行于支持 Servlet 3.1 的 Server 容器上(Servlet 3.1 開始支持異步)
如圖示,左側(cè)是傳統(tǒng)的 Spring MVC 結(jié)構(gòu), 右側(cè)是 webflux 組件。
為了讓大家更好利用 webflux 編程,Spring 貼心地兼容了 @Controller 等 Spring MVC 的注解在 webflux 的使用,能讓使用者更好地過渡到 webflux 編程中來,不過在底層實(shí)現(xiàn)中,與 Spring MVC 的實(shí)現(xiàn)的請(qǐng)求 InputStream 和響應(yīng) OutputStream 不同,webflux 實(shí)現(xiàn)了一套反應(yīng)式的請(qǐng)求(ServerHttpRequest) 和響應(yīng)(ServerHttpResponse),這兩個(gè)類將請(qǐng)求體與響應(yīng)體以 Flux(Flux 下文會(huì)簡(jiǎn)單介紹下)的形式暴露出來,同時(shí) webflux 底層也實(shí)現(xiàn)了基于 Flux的 JSON,XML 的序列化和反序列化,HTML 實(shí)圖的渲染,Server 發(fā)送事件等。
通過介紹可以看到 webflux 實(shí)現(xiàn)了從請(qǐng)求到響應(yīng),到渲染,事件發(fā)送等一整套反應(yīng)式事件的支持,是的,要最大程度地發(fā)揮 webflux 的性能,中間所有的事件都應(yīng)該以 Mono 或 Flux 響應(yīng)式事件流的形式存在!
WebFlux 的底層實(shí)現(xiàn)其實(shí)是基于 Reactor 實(shí)現(xiàn)的,在 Reactor 的核心類中,以下兩個(gè)類代表了發(fā)布者
Mono: 代表 0 到 1 個(gè)元素的發(fā)布者
Flux:代表 0 到 N 個(gè)元素的發(fā)布者
這玩意怎么用呢,如下圖示
@RequestMapping("/demo") @RestController public class DemoController { @RequestMapping(value = "/foobar") public Mono<Foobar> foobar() { return Mono.just(new Foobar()); } }
本來是要返回 foobar 對(duì)象的,結(jié)果最終以 Mono(或 Flux)的形式存在,這樣就構(gòu)建了響應(yīng)式編程中的生產(chǎn)者(Publisher),再調(diào)用 subscribe 即可完成對(duì)生產(chǎn)者的監(jiān)聽消費(fèi)。
在我們的網(wǎng)關(guān)設(shè)計(jì)中,當(dāng)收到請(qǐng)求后,使用了 Mono 來充當(dāng)發(fā)布者,如果中間出現(xiàn)了問題,會(huì)調(diào)用 onError, 最終成功后會(huì)調(diào)用 onSuccess,以下是網(wǎng)關(guān)實(shí)現(xiàn)采用的總體框架。
圖中 Mono.empty 代表創(chuàng)建一個(gè)不包含任何元素,只發(fā)布消息的隊(duì)列。發(fā)送消息后,會(huì)在線程池里處理網(wǎng)關(guān)的 slot ,最后處理成功后會(huì)調(diào)用 onSuccess 方法,處理失敗則會(huì)調(diào)用 onError。下一節(jié)我們來看看這些網(wǎng)關(guān) slot 是如何處理的。
網(wǎng)關(guān)的責(zé)任鏈設(shè)計(jì)
不管是核心網(wǎng)關(guān)還是嵌入式網(wǎng)關(guān)我們都采用了責(zé)任鏈模式來實(shí)現(xiàn)網(wǎng)關(guān)的核心處理流程,將每個(gè)處理邏輯看成一個(gè)slot,每個(gè) slot 按照預(yù)先設(shè)定的順序先后執(zhí)行,與開源kong,zuul等類似,我們也采用了PRPE模式(Pre、Routing、Post、Error)
Pre 階段:
initParamsSlot 初始化組裝請(qǐng)求上下文參數(shù)
sentinelSlot 流控組件引入 ,做集群限流、降級(jí)、熔斷使用
riskSlot 風(fēng)控處理
Route 階段:
dubboSlot 通過 dubbo 泛化調(diào)用轉(zhuǎn)換成 dubbo 協(xié)議進(jìn)行遠(yuǎn)程調(diào)用
POST Slots: 后置處理
APMMonitorSlot APM 監(jiān)控處理,請(qǐng)求出錯(cuò)等打點(diǎn)監(jiān)控
采用這樣的設(shè)計(jì)方式,各個(gè) slot 各司其職,也有較好的可擴(kuò)展性,如果還想加什么 slot,定義好此 slot 功能,指定好其在調(diào)用鏈中的位置即可。
需要注意的是有些 Slot 的請(qǐng)求結(jié)果依賴于前面 Slot 的執(zhí)行結(jié)果,這種情況下需要對(duì)前面的執(zhí)行事件用 Mono 的形式封裝起來,這樣這些 slot 就構(gòu)成了一個(gè)個(gè)的響應(yīng)式事件流,保證了這些 Slot 都是異步執(zhí)行的,不會(huì)阻塞主線程。
另外注意高亮的 dubboSlot 階段,在 dubbo 2.7 之前 dubbo 底層返回 Future(會(huì)一直占用一個(gè)線程輪詢結(jié)果),對(duì)異步編程不友好,2.7 之后返回了 CompleteFuture,與 webflux 的異步編程模型完美結(jié)合(發(fā)起調(diào)用嵌入式網(wǎng)關(guān)后立馬返回,等調(diào)用完成后才執(zhí)行,是真正的異步)。
嵌入式網(wǎng)關(guān)設(shè)計(jì)
首先我們要明白為啥會(huì)有嵌入式網(wǎng)關(guān)的需求,主要有三個(gè)原因
目前有 H5, 小程序,app 端,各端的 session 存儲(chǔ)不一樣,需要根據(jù)請(qǐng)求的各端來查找 session 對(duì)應(yīng)的 uid,這個(gè)操作顯然應(yīng)該在網(wǎng)關(guān)層面來做,放在嵌入式網(wǎng)關(guān)來實(shí)現(xiàn)更合理
每個(gè)請(qǐng)求進(jìn)入業(yè)務(wù)層之后,我們需要對(duì)其時(shí)間戳,app 簽名,小程序簽名等進(jìn)行校驗(yàn),這些校驗(yàn)對(duì)每個(gè)端的請(qǐng)求都是必要的,所以顯然應(yīng)該在網(wǎng)關(guān)來做
有些業(yè)務(wù)需要在執(zhí)行業(yè)務(wù)前后做一些擴(kuò)展,比如執(zhí)行前后需要打點(diǎn)分析等,對(duì)擴(kuò)展的實(shí)現(xiàn)網(wǎng)關(guān)也應(yīng)該支持
那么嵌入式網(wǎng)關(guān)如何實(shí)現(xiàn)呢,業(yè)務(wù)服務(wù)是以 dubbo 服務(wù)的形式存在的,而在 dubbo 中有一個(gè) Filter 機(jī)制,是專門為服務(wù)提供方和服務(wù)消費(fèi)方調(diào)用過程進(jìn)行攔截設(shè)計(jì)的,每次遠(yuǎn)程方法執(zhí)行,該攔截都會(huì)被執(zhí)行。這樣就為開發(fā)者提供了非常方便的擴(kuò)展性,所以嵌入式網(wǎng)關(guān)的主要設(shè)計(jì)思路就是自定義 dubbo 的 filter,然后在此 filter 中執(zhí)行相關(guān)的擴(kuò)展邏輯即可,偽代碼如下:
這樣通過自定義 filter 的方式我們解決了擴(kuò)展性的問題,注意我們使用了Activate注解,這樣 dubbo 就會(huì)把注釋的Filter 作為 dubbo 原生的 Filter 自動(dòng)加載,而不需要顯示的配置 provider 或者 consumer 的 filter,也就避免了對(duì)代碼的侵入性。
這里的業(yè)務(wù)邏輯執(zhí)行前后的擴(kuò)展也是通過責(zé)任鏈的模式來執(zhí)行一個(gè)個(gè)的的 slot, 我們先定義好時(shí)間戳校驗(yàn),簽名校驗(yàn),Session轉(zhuǎn)id等 slot, 然后在 xml 中指定這些 slot 的執(zhí)行順序
每個(gè)業(yè)務(wù)都有一個(gè) gateway.xml 文件,可以在此文件中配置 H5, app, 小程序需要執(zhí)行的 slot。
以對(duì) app 請(qǐng)求配置需要執(zhí)行的前置 slot 和后置處理 slot 為例 ,偽代碼如下
這樣只要在啟動(dòng)函數(shù)中引入(ImportResource)需要支持的 gateway 的 xml 文件,配置的 bean 就能生效,然后在 filter 中會(huì)分別取 bizChannel(請(qǐng)求必傳,代表是業(yè)務(wù)哪一端標(biāo)識(shí),如 biz_h6, biz_app, biz_小程序)對(duì)應(yīng)的 slotBizList 即可執(zhí)行業(yè)務(wù)邏輯前后的擴(kuò)展。
通過這樣的方式就有效地指定了業(yè)務(wù)邏輯執(zhí)行前后需要執(zhí)行的 slot,每個(gè)業(yè)務(wù)如果想在業(yè)務(wù)邏輯執(zhí)行前后進(jìn)行擴(kuò)展,只要定義好自己的 slot 邏輯,在 xml 文件中指定此 slot 的位置即可生效。
嵌入式網(wǎng)關(guān)按以上思路實(shí)現(xiàn)后,就通過 jar 包分發(fā)到各個(gè)業(yè)務(wù)系統(tǒng)。好處是:穩(wěn)定性提升,每個(gè)業(yè)務(wù)集成一個(gè)穩(wěn)定版本的網(wǎng)關(guān) Jar,某一個(gè)業(yè)務(wù)系統(tǒng)做網(wǎng)關(guān) Jar 升級(jí)時(shí),其他業(yè)務(wù)系統(tǒng)都不受干擾
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。
文章題目:Java中怎么設(shè)計(jì)應(yīng)用層網(wǎng)關(guān)
文章地址:http://vcdvsql.cn/article10/gjepgo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、營銷型網(wǎng)站建設(shè)、App設(shè)計(jì)、網(wǎng)站改版、域名注冊(cè)、手機(jī)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)