|
一,支持接口限流,避免惡意請(qǐng)求導(dǎo)致服務(wù)層壓力過大 常見的限流功能一般有兩個(gè)關(guān)注點(diǎn): 1.限流原則,即以什么樣的條件對(duì)請(qǐng)求進(jìn)行識(shí)別以及放行。常見的作法是給予每個(gè)調(diào)用API的系統(tǒng)不同的唯一編碼,用于監(jiān)控某一編碼的調(diào)用是否超出上限。 2.限流機(jī)制,即通過什么樣的機(jī)制實(shí)現(xiàn)限流。常見的作法是通過Redis中Key的TTL失效機(jī)制來限制訪問頻率。 比較常見的處理方案是使用Redis中Key的TTL失效機(jī)制來限制訪問頻率,然后通過切面實(shí)現(xiàn)處理限流邏輯,并使用自定義注解將零散的關(guān)注點(diǎn)集中起來。 接下來按照上述兩個(gè)關(guān)注點(diǎn)對(duì)EL-ADMIN中如何實(shí)現(xiàn)的接口限流進(jìn)行拆解: 首先是它的限流原則:
圖一,限流使用實(shí)例 通過me.zhengjie.modules.system.rest.LimitController的test方法就可以看到EL-ADMIN對(duì)限流的封裝還是比較易用的,只需通過注解設(shè)置指定時(shí)間段內(nèi)允許訪問的次數(shù)以及限流的鍵名(前綴+key),其中name是指的該接口的功能備注,并不會(huì)影響限流的功能,只是方便在日志中檢索對(duì)應(yīng)接口的信息。
圖二,限流切面 但是不止于此(見上圖),進(jìn)一步挖掘處理這個(gè)注解的Around類型切面(me.zhengjie.aspect.LimitAspect)的時(shí)候發(fā)現(xiàn)限流的原則不止是通過key的方式還有通過客戶端IP的方式來限制,Around切面可以理解為在所有Limit注解處添加了一個(gè)執(zhí)行攔截器,判斷是否滿足允許執(zhí)行的條件后通過joinPoint.proceed()執(zhí)行原來的方法(這里類似攔截器的過濾鏈處理模式)??偨Y(jié)如下:在使用Redis作為限流核心機(jī)制的設(shè)計(jì)方案中,Key的設(shè)置直接決定了限流原則。 其次是它的限流機(jī)制: 通過圖二的限流切面分析可知,相比于一般的限流邏輯,EL-ADMIN進(jìn)一步對(duì)Redis的操作做了優(yōu)化,使用了Lua腳本來執(zhí)行限流的判斷機(jī)制。具體的限流機(jī)制是通過將接口地址結(jié)合Limit注解的prefix+key+url的方式構(gòu)建了一個(gè)接口對(duì)應(yīng)的唯一鍵,通過查詢Redis上該鍵TTL(生命周期period)時(shí)間段內(nèi)的值(訪問次數(shù)count)實(shí)現(xiàn)限流的邏輯。限流機(jī)制這部分的核心Lua實(shí)現(xiàn)如下圖所示,需要注意的是在第6行和第7行,在Redis中假如直接對(duì)一個(gè)key執(zhí)行incr命令會(huì)將這個(gè)key設(shè)置為1。
圖三,限流機(jī)制Lua腳本 另外相比一般場(chǎng)景下使用RedisTemplate執(zhí)行這一系列操作,使用Lua腳本操作Redis的優(yōu)勢(shì)如下: 1.保證對(duì)Redis操作的原子性,防止出現(xiàn)對(duì)同一個(gè)key的非原子性操作。 2.減少網(wǎng)絡(luò)數(shù)據(jù)傳輸節(jié)省帶寬。 二,支持接口級(jí)別的功能權(quán)限與數(shù)據(jù)權(quán)限,可自定義操作 這里提到的功能權(quán)限一般是指使用者能否使用系統(tǒng)的功能,數(shù)據(jù)權(quán)限同理則是指使用者能否訪問對(duì)應(yīng)的數(shù)據(jù),如果看過上一篇《RuoYi-Vue學(xué)習(xí)筆記-分析》就不難理解這里的數(shù)據(jù)權(quán)限的使用場(chǎng)景了。 一般的功能權(quán)限都是基于RBAC思想設(shè)置一套基于角色的權(quán)限授予方案,讓用戶的權(quán)限通過角色這一層抽象和系統(tǒng)中的權(quán)限發(fā)生交互。 常見的功能權(quán)限的解決方案是使用Spring Security + Jwt Token前者提供了成套的權(quán)限過濾器,只需要將我們?cè)O(shè)計(jì)權(quán)限系統(tǒng)的判斷邏輯接入過濾器即可,后者則有效的解決了客戶端身份盜用以及服務(wù)端的橫向擴(kuò)展的問題。數(shù)據(jù)權(quán)限在使用MyBatis框架時(shí)可以通過超類冗余字段+請(qǐng)求處理切面實(shí)現(xiàn),在使用Spring Boot Jpa中的具體實(shí)現(xiàn)可以探索一番。在這一章節(jié)我們可以拆解以下兩個(gè)功能的具體實(shí)現(xiàn): 1.Spring Boot Jpa的數(shù)據(jù)權(quán)限解決方案的實(shí)現(xiàn)。 2.基于Spring Security框架實(shí)現(xiàn)的功能權(quán)限如何實(shí)現(xiàn)全局接口自定義權(quán)限放行,即通常情況下需要使用@PreAuthorize("hasAnyRole('admin','menu:edit')")這類注解才能讓接口放行,但是超管可以訪問所有的接口,那么我們就要給每個(gè)接口都添加admin的注解值了,這樣做是效率很低的,通過全局接口自定義權(quán)限放行的方案即可實(shí)現(xiàn)在一處配置即可全局接口免去這個(gè)配置(這一特性的具體實(shí)現(xiàn)在之后的第三章節(jié)進(jìn)行解構(gòu))。 首先,一起來看看EL-ADMIN對(duì)數(shù)據(jù)權(quán)限的封裝,在拆解這部分之前需要先對(duì)Jpa有個(gè)大概的了解,不然會(huì)對(duì)這部分實(shí)現(xiàn)拆解的理解會(huì)有影響。當(dāng)下常見的應(yīng)用訪問數(shù)據(jù)的方式有以下兩種,一種是以Hibernate為首的信奉"程序員就不該寫SQL"的一眾ORM框架,這類框架不會(huì)讓程序員寫一行SQL,統(tǒng)統(tǒng)都交給框架處理,與之對(duì)應(yīng)的則是封裝了SQL變化的一眾注解以及類(個(gè)人感覺這個(gè)抽象層的機(jī)制比SQL還難...);一種是以MyBatis為代表的通過JDBC橋接了數(shù)據(jù)庫,讓程序員能夠靈活的自定義SQL語句來執(zhí)行,這類框架則是注重靈活,與之對(duì)應(yīng)的則是需要有一定的SQL基礎(chǔ)。 JPA就是Hibernate所遵守的標(biāo)準(zhǔn)名稱,使用這個(gè)標(biāo)準(zhǔn)的框架可以通過以下列表的注解實(shí)現(xiàn)SQL語句的對(duì)等功能。
圖四,JPA注解及其功能 參考RuoYi對(duì)數(shù)據(jù)權(quán)限的處理,要想實(shí)現(xiàn)數(shù)據(jù)權(quán)限至少有兩個(gè)點(diǎn)需要關(guān)注, 一是查詢前用戶數(shù)據(jù)權(quán)限的獲取; 二是查詢時(shí)數(shù)據(jù)權(quán)限注入; 首先是用戶數(shù)據(jù)權(quán)限的獲取,這部分?jǐn)?shù)據(jù)一般是通過登錄的時(shí)候放置在用戶的cookies中或是登錄后的session中,這部分在EL-ADMIN中是通過下圖中的幾步實(shí)現(xiàn)的,需要注意的是使用了Vuex的異步接口請(qǐng)求的功能(圖中第一步)。
圖五,前端登錄流程 至此,系統(tǒng)已經(jīng)獲取到了用戶的數(shù)據(jù)權(quán)限的信息,系統(tǒng)前端請(qǐng)求接口的時(shí)候攜帶對(duì)應(yīng)的用戶Token即可,后端接口在登錄的時(shí)候已經(jīng)對(duì)Token和用戶信息(包含了用戶數(shù)據(jù)權(quán)限記錄)進(jìn)行了綁定和Redis緩存,便于接口調(diào)用的時(shí)候直接通過緩存獲取。接下來就是第二步查詢時(shí)數(shù)據(jù)權(quán)限注入的拆解,這部分RuoYi是使用了注解+切面+超類基本和業(yè)務(wù)代碼不侵入的方式實(shí)現(xiàn)的,EL-ADMIN的實(shí)現(xiàn)對(duì)比之下和業(yè)務(wù)代碼的耦合性較大(這個(gè)鍋其實(shí)和JPA的設(shè)計(jì)哲學(xué)“程序員絕不寫SQL”有關(guān),也不能讓EL-ADMIN背)。如下圖所示,自定義數(shù)據(jù)權(quán)限注解在這個(gè)功能中的職責(zé)一如既往的是用來實(shí)現(xiàn)查詢條件以及字段的個(gè)性化的注入到查詢過程中。
圖六,后端數(shù)據(jù)權(quán)限查詢拼接流程 上圖中需要注意的有這么兩點(diǎn): 1.左側(cè)line62,數(shù)據(jù)權(quán)限信息獲取中是通過Spring Security提供的用戶信息容器SecurityContextHolder獲取的,可以理解為通過線程池綁定了用戶信息構(gòu)成的上下文。 2.右側(cè)line45同理,也是通過1中的這個(gè)上下文獲取的用戶對(duì)應(yīng)的數(shù)據(jù)權(quán)限信息。 三,自定義權(quán)限注解與匿名接口注解,可快速對(duì)接口攔截與放行 這個(gè)特性其實(shí)是特性二的補(bǔ)充,相當(dāng)于在接口前做了一個(gè)切面,針對(duì)特定的角色可以不去做權(quán)限校驗(yàn)。針對(duì)這個(gè)特性,一般的解決方案是通過注解對(duì)接口的權(quán)限進(jìn)行標(biāo)記,然后通過切面/攔截器/過濾器對(duì)接口調(diào)用的請(qǐng)求進(jìn)行匹配判斷。至此引出這一特性的幾個(gè)關(guān)注點(diǎn): 1.用于標(biāo)記攔截以及放行的自定義注解 2.整合入Spring Security的切面/過濾器/攔截器 首先關(guān)注攔截以及放行的注解,這部分標(biāo)記權(quán)限攔截的注解使用了常見的@PreAuthorize,只不過其中的值是使用了"@el.check('****')"這種形式的字符串。這部分是通過SpringEL表達(dá)式獲取了容器中的Bean,然后計(jì)算對(duì)應(yīng)的結(jié)果,這部分邏輯可以參考原始的hasAnyRole方法返回的結(jié)果來實(shí)現(xiàn)的。究其本質(zhì)就是針對(duì)權(quán)限判斷這部分,框架其他的部分不做變更,添加一個(gè)模塊輸入輸出的類型不變,那么就是可以契合原有框架的,通過下圖的原生的寫法比對(duì)更好理解一些,可以看到兩者的返回類型都是布爾值,且根據(jù)面向接口編程的規(guī)范我們甚至可以實(shí)現(xiàn)一個(gè)該方法所在的接口實(shí)現(xiàn)類來定制我們自己的權(quán)限判斷邏輯。
圖七,自定義權(quán)限判斷規(guī)則 放行的則是使用了自定義的注解,值得一看的是EL-ADMIN通過對(duì)Spring MVC的@RequestMapping注解的擴(kuò)展實(shí)現(xiàn)了框架需要的功能,充分的體現(xiàn)了六大設(shè)計(jì)原則之一的開閉原則。如下圖所示,放行的注解有以下兩種使用場(chǎng)景,一是針對(duì)現(xiàn)有代碼直接添加注解即可,一是針對(duì)新的方法可以把對(duì)應(yīng)的注解當(dāng)作SpringMVC框架原生的注解使用。
圖八,自定義注解的放行策略 自定義注解實(shí)現(xiàn)的放行策略這部分需要注意的有幾處: 1.通過圖八中的方法,通過覆蓋原有的注解實(shí)現(xiàn)帶有權(quán)限放行+路由功能的自定義注解。 2.自定義權(quán)限放行和Spring Security框架的整合,通過SpringMVC中提供的Bean收集所有帶有放行注解的路由實(shí)現(xiàn)無配置自動(dòng)化的放行。 3.放行所有的Options為HTTP請(qǐng)求動(dòng)詞的請(qǐng)求,這是因?yàn)榭缬蛘?qǐng)求需要用到這類動(dòng)詞的請(qǐng)求,攔截就無法實(shí)現(xiàn)跨域了。 四,前后端統(tǒng)一異常攔截處理,統(tǒng)一輸出異常,避免繁瑣的判斷 一般來說統(tǒng)一的異常攔截都可以通過過濾器或是攔截器實(shí)現(xiàn),Spring很貼心的幫我們做了這部分的封裝,這一章節(jié)主要是拆解前后端結(jié)合的異常攔截的工程實(shí)踐方案。 1.首先需要在前端對(duì)響應(yīng)有全局的處理邏輯,讓所有的響應(yīng)都能通過前端邏輯進(jìn)行轉(zhuǎn)化提示,說白了就是axios做的事情了。 2.然后在后端通過過濾器,切面,攔截器等等實(shí)現(xiàn)對(duì)響應(yīng)的修改操作。 這里主要對(duì)后端的實(shí)現(xiàn)做拆解,常見的異常處理機(jī)制都是在業(yè)務(wù)層、控制層拋出,通過@ExceptionHandler注解處理異常拋出的過程。該機(jī)制的實(shí)現(xiàn)是通過切面實(shí)現(xiàn)的,其中切面的切點(diǎn)聲明是通過注解@RestControllerAdvice或@ControllerAdvice實(shí)現(xiàn)。 基于切面,這部分功能特性可以做的拓展有異常處理(注解@ExceptionHandler),初始化數(shù)據(jù)綁定器用于特定數(shù)據(jù)格式的參數(shù)綁定(注解@InitBinder),特殊請(qǐng)求參數(shù)的綁定(注解@ModelAttribute)。對(duì)應(yīng)的使用場(chǎng)景分別是,根據(jù)拋出的不同的異常封裝不同的請(qǐng)求流轉(zhuǎn)以及響應(yīng),將前端請(qǐng)求的字符串類型的日期轉(zhuǎn)化為Date類型的日期,不通過控制器層將參數(shù)綁定到Model中。 總之,將@ControllerAdvice這個(gè)注解理解為在過濾器,攔截器,之后的最后一道攔在請(qǐng)求和我們應(yīng)用的處理邏輯之間的處理流程即可。通過這個(gè)切面結(jié)合注解@ExceptionHandler可以將控制器層以及業(yè)務(wù)層拋出的所有異常根據(jù)異常類型,異常所屬的包進(jìn)行差異化的攔截處理。 五,支持運(yùn)維管理,可方便地對(duì)遠(yuǎn)程服務(wù)器的應(yīng)用進(jìn)行部署與管理 這個(gè)特性已經(jīng)讓EL-ADMIN脫離了后端框架的定位了,雖然這個(gè)功能還是比較常用的,但是這個(gè)特性中包含的功能只要單獨(dú)拿出來,然后稍微拓展就可以當(dāng)作服務(wù)器管理工具來用了。 通過官方文檔的描述可以把重點(diǎn)放在這幾個(gè)功能的實(shí)現(xiàn)上:
通過下圖九可以更好的理解上述的特性點(diǎn),首先是第一步,用戶通過本地和EL-ADMIN的交互將應(yīng)用包上傳到服務(wù)器上,此時(shí)應(yīng)用文件會(huì)通過相關(guān)的配置參數(shù)推送到遠(yuǎn)端的服務(wù)器,然后通過部署腳本進(jìn)行部署。這里比較關(guān)鍵點(diǎn)是需要能夠監(jiān)控遠(yuǎn)端服務(wù)器此時(shí)指定的應(yīng)用的狀態(tài),一般使用微服務(wù)架構(gòu)的應(yīng)用可以使用服務(wù)注冊(cè)與發(fā)現(xiàn)中心來實(shí)現(xiàn)這個(gè)監(jiān)控,如果是單體的巨石應(yīng)用則可以監(jiān)控對(duì)應(yīng)的端口進(jìn)程狀態(tài)即可。
圖九,用戶對(duì)遠(yuǎn)程服務(wù)器應(yīng)用部署與管理 上述幾個(gè)功能用到了兩個(gè)開源組件以及一個(gè)之前所沒有接觸過的技術(shù)點(diǎn),分別是: jsch,連接指定的機(jī)器后執(zhí)行shell命令,并獲取對(duì)應(yīng)的返回值。 ganymed-ssh2,連接指定的機(jī)器后獲取文件或上傳文件的工具。 websocket,這個(gè)技術(shù)的確是做CURD Boy不經(jīng)常接觸的技術(shù)點(diǎn),定義是這樣描述的: WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。 這里有兩個(gè)點(diǎn)需要解釋一下,一個(gè)是單個(gè),怎么理解這個(gè)單個(gè)呢,一般來說你在網(wǎng)頁上點(diǎn)擊一個(gè)超鏈接就是單個(gè)的概念了,一個(gè)請(qǐng)求就是單個(gè)TCP連接了。其次就是全雙工的定義,這是一個(gè)通訊概念,同屬一類的有單工,半雙工,全雙工。他們的區(qū)別就是從兩個(gè)維度上來區(qū)分,其一是信息的傳遞是單向的還是雙向的,單向的就是單工,雙向的就是雙工。其二是發(fā)送和接受能否同時(shí)執(zhí)行,能夠就是全雙工,不能就是半雙工。對(duì)應(yīng)我們實(shí)際在開發(fā)中使用到的通信技術(shù)和協(xié)議,http,ajax這些就是半雙工的,因?yàn)檎?qǐng)求時(shí)信息的流向時(shí)雙向的,但是不能同時(shí)進(jìn)行接受信息和發(fā)送信息,websocket則是提供了一種在發(fā)送消息的時(shí)候接受消息的技術(shù)標(biāo)準(zhǔn)。 有了websocket這個(gè)新玩具, EL-ADMIN把它用到了遠(yuǎn)端服務(wù)器狀態(tài)的實(shí)時(shí)信息推送,比如應(yīng)用部署完成,應(yīng)用卸載,應(yīng)用當(dāng)前運(yùn)行狀態(tài)的獲取等等。在廣闊的業(yè)務(wù)場(chǎng)景中,因?yàn)?/span>websocket的實(shí)時(shí)性比ajax要高的多,所以也可以用到聊天的場(chǎng)景中。 參考文獻(xiàn): [0]EL-ADMIN特性https:///guide/kslj.html#%E9%A1%B9%E7%9B%AE%E7%AE%80%E4%BB%8B [1]Lua語法https://www.runoob.com/lua/lua-basic-syntax.html [2]Redis中使用Lua腳本https://www.cnblogs.com/kaituorensheng/p/11098194.html [3]JPA常見注解https://blog.csdn.net/wang_1220/article/details/107915815 [4]數(shù)據(jù)權(quán)限https://www.cnblogs.com/anderson-question/articles/14907553.html [5]JpaSpecificationExecutor的使用https://zhuanlan.zhihu.com/p/139107843
|
|
|