一,還原論
還原論或還原主義(英語:Reductionism,又譯化約論),是一種哲學思想,認為復雜的系統、事物、現象可以將其化解為各部分之組合來加以理解和描述。
為了防止掉入過度還原的陷阱,我們可以將系統的功能從前端到后端依次分析拆分,最后再拼接起來。在web系統中一個請求天然的可以將前后端串聯起來,跟蹤一個請求的流轉即可還原出系統的內部處理邏輯;微服務中的 Sleuth+Zipkin等等鏈路追蹤的系統的思想也是基於“完整服務一個請求”來處理的。
二,功能列表
從RuoYi的官網摘抄了它的特性以及功能,比較感興趣相關的落地實踐的功能如下:
功能/特性
|
備注
|
在線用戶:當前系統中活躍用戶狀態監控。
|
如何識別用戶在線?
|
定時任務:在線(添加、修改、刪除)任務調度包含執行結果日志。
|
實時的任務調度,可以從某種程度上實現對ETL服務器的管理 |
代碼生成:前后端代碼的生成(java、html、xml、sql)支持CRUD下載。
|
CURDBoy福音
|
服務監控:監視當前系統CPU、內存、磁盤、堆棧等相關信息。
|
通過JVM監控操作系統參數?
|
完全響應式布局(支持電腦、平板、手機等所有主流設備)
|
|
支持多數據源,簡單配置即可實現切換。
|
|
支持按鈕及數據權限,可自定義部門數據權限。
|
數據權限如何實現
|
完善的XSS防范及腳本過濾,徹底杜絕XSS攻擊
|
相比常見的xss完善了什么?
|
Maven多項目依賴,模塊及插件分項目,盡量松耦合,方便模塊升級、增減模塊。
|
多模的項目結構參考 |
三,在線用戶
通過系統監控->在線用戶功能入口訪問對應的頁面,因為請求通過了視圖層的轉發,這里就不使用瀏覽器地址欄的URL作為請求追蹤依據了,查看對應的請求如下圖。通過其中的“monitor/online”定位到對應的控制器。

查看對應控制器的邏輯是從
Redis以及
MySQL數據庫中查詢到指定的數據而后輸出到頁面,其中關鍵點在查詢
Redis上登錄
TokenID對應的鍵值。逆向思考一下就可以想到,向
Redis中設置這個值並更新對應值的方法就是用戶識別用戶是否在線以及強退的關鍵。順着這個思路就可以找到
TokenService這個處理用戶票據的類,如下圖所示:

可以看到
Token的生成以及刷新的方法,查看各個方法的調用關系樹后發現,登錄時會生成對應用戶的
Token,而后將其存儲在每個請求的Header中,每次訪問系統的時候都會通過過濾器檢查該
Token的有效性,從而實現了在線用戶的列表獲取,以及強退指定的用戶。要想理清這部分邏輯需要對當前請求在
RuoYi中的流轉過程進行如下兩個方向的梳理。

從
axios處可以找到每個請求都通過
config.headers['Authorization'] = 'Bearer ' + getToken()設置了token到登錄的請求頭中,從后端的角度查看過濾器有
JwtAuthenticationTokenFilter該過濾器通過繼承Spring Security提供的
OncePerRequestFilter過濾器對每個請求進行一次過濾,從而實現對每個請求都進行在線狀態管理的能力。至於強制退出,只需要刪除用戶對應的Redis上的記錄即可,這里就不再贅述,感興趣的朋友可以去閱讀
TokenService的源碼。
四,定時任務
一般定時任務的使用場景有以下幾種場景
ETL每月跑數據的任務,清理冗余數據,生成報表。比較常見的定時任務都是通過注解的方式固定cron表達式來確定執行的周期以及時間,RuoYi可以動態觸發以及實時的修改是否執行,這點可以深入的拆解分析下。參考“在線用戶”功能的分析方案,通過前端創建任務入口
@click="handleRun(scope.row)"對應調用的api地址是
url: '/monitor/job/run',映射到后端的入口如下
圖所示:

核心功能的實現和
Scheduler以及
ScheduleUtils相關,具體分析這兩個類的方法。分析對應方法可知,前者提供了定時任務的操作接口,后者主要是對前者提供接口接口包裝實現一些具體的功能,例如獲取定時任務的id或獲取任務的執行狀態等。
五,代碼生成
大部分項目里其實有很多代碼都是重復的,幾乎每個基礎模塊的代碼都有增刪改查的功能,而這些功能都是大同小異, 如果這些功能都要自己去寫,將會大大浪費我們的精力降低效率。所以這種重復性的代碼可以使用代碼生成。
通過官網摘抄對應的功能描述,分解其中的技術點可以得出,要想能夠根據表格生成代碼需要解決以下幾個技術問題:通過數據庫獲取表列表以及表結構,通過表結構生成模板類型的代碼(包括Java代碼,xml代碼,sql語句,vue視圖,js腳本)。
表中的信息提取可以通過如下兩個sql獲取:
1.獲取指定數據庫下的表列表,帶有數據庫注釋

2.獲取指定表的所有列

各類源碼的模板文件的生成就簡單了,使用Velocity模板引擎就可以在編寫了模板后生成指定格式的文件了。
六,服務監控
通過分析
com.ruoyi.framework.web.domain下的Server源碼發現調用了
com.sun.jna包下的一些方法。Java具有“一次編寫,處處運行”的特性,是因為在運行時的時候,程序是運行在JVM上的,而JVM又是建立在操作系統之上的,所以通過Java來直接操作硬件或是獲取硬件信息這個需求還是有點難度的。不過還好,Sun公司提供了JNI的機制,讓Java使用者可以通過JNI的方式調用其他語言的程序例如通過Java調用C++。這里的服務監控就是通過OSHI實現的
( OSHI is a free JNA-based (native) Operating System and Hardware Information library for Java. It does not require the installation of any additional native libraries and aims to provide a cross-platform implementation to retrieve system information, such as OS version, processes, memory and CPU usage, disks and partitions, devices, sensors, etc. 詳見:https://github.com/oshi/oshi),它的
原理就是通過JNA(包裝過的JNI)的方式調用C的方法從而獲取對應的硬件信息。
七,前端的完全響應式布局
這部分歸功於集成了ElementUI的Vue,在這就不再贅述了。(畢竟不是專業的前端工程師,也說不出個一二三,哈哈哈)
八,多數據源的支持
這里其實是對SpringBoot已經支持的多數據源進行了一個切面的包裝,我們從使用的方式入手分析一下底層的原理。
使用步驟:
1.在需要切換數據源Service或Mapper方法上添加@DataSource注解
@DataSource(value = DataSourceType.MASTER),其中value用來表示數據源名稱
2.在
application-druid.yml配置從庫數據源,這一步主要是為了提供連接數據庫相關的信息。
3.在
DataSourceType類添加數據源枚舉,全局統一的數據源名稱,防止錯誤的引用。
4.在
DruidConfig配置讀取數據源,這一步才將數據源裝配到
Spring提供的
IOC容器中。
5.在
DruidConfig類
dataSource方法添加數據源,這里通過
AbstractRoutingDataSource實現了一個能夠切換數據源的
Bean:
DynamicDataSource
這一步可以進一步深挖,再進一步分析源碼可以看到,使用的數據源都是通過
DynamicDataSourceContextHolder提供的。至此,數據源的切換邏輯已經可以梳理出來了。通過給每個數據源全局唯一的一個name,然后將數據源以及這個name放置在線程池中。其中的關鍵點在,Spring提供的
AbstractRoutingDataSource類提供了能夠通過一個key來實現在多個數據源之間路由的機制。
6.在需要使用多數據源方法或類上添加
@DataSource注解,其中
value用來表示數據源。這里是對方法上添加注解實現對方法級別或是
service級別打標簽,從而實現在方法執行的時候切面
DataSourceAspect能夠根據標簽決定使用哪一個數據庫執行對應的sql。
九,數據權限
一般后端框架比較常見的權限體系大多是 基於角色的訪問控制(
RBAC),而這種授權體系有一個弊端就是沒有職位,崗位之間的數據權限區分。
例如這個應用場景,A和B分別是不同團隊的leader,要想A能夠看到A團隊所有人的數據,但是不能看到B團隊成員的數據,一般的思路都是建立對應的角色后根據角色來實現。但是日常的情況是兩個團隊又是同一個部門下的,這時就需要用到數據權限了。RuoYi提供的使用場景描述如下:
在實際開發中,需要設置用戶只能查看哪些部門的數據,這種情況一般稱為數據權限。例如對於銷售,財務的數據,它們是非常敏感的,因此要求對數據權限進行控制, 對於基於集團性的應用系統而言,就更多需要控制好各自公司的數據了。如設置只能看本公司、或者本部門的數據,對於特殊的領導,可能需要跨部門的數據, 因此程序不能硬編碼那個領導該訪問哪些數據,需要進行后台的權限和數據權限的控制。
數據權限的實現比較直接(框架級別的SpringSecurity的整合就不再贅述),是通過
注解(@DataScope)+切面(DataScopeAspect)+超類參數綁定(BaseEntity)實現的。
值得一提的是,這個超類的參數綁定是比較有意思的,用戶的數據權限在用戶發起請求的時候,切面會根據注解的標簽識別到這個請求,然后根據不同的用戶,以及數據權限類型拼接動態sql並放置到超類的冗余參數字段,最后ORM框架則會從超類中取出對應的參數執行查詢。這個處理流程很好的體現了作者對里氏代換原則以及迪米特法則的使用,所有的子類都可以用超類替換,切面並不知曉實際執行的sql。
十,完善的XSS機制
日常我們比較常見的Web安全漏洞大多都是XSS攻擊導致的,通俗一點的表現就是用戶在需要提交的表單中提交了一些可以由瀏覽器執行或渲染的腳本,導致應用的功能或數據的損壞以及泄露。一般的處理方案都是通過一個全局的過濾器,然后通過關鍵字匹配實現不安全的參數的過濾。方法是比較簡單,方案也通俗易懂,但是總是會有一些這樣或那樣的特例漏掉,這里使用了“HTMLFilter”來防止出現遺漏的問題,另外這里有一個比較難以察覺的坑,request的流式讀取只允許一次,也就是說如果在過濾器或是切面中對request的流進行了讀取,那么后續處理請求就無法獲取到對應的流中的信息了。通用的解決方案是擴展HttpServletRequestWrapper這一J2ee提供的包裝類,在其中設置一個公共成員,把流中的信息存儲在這里就可以避免流的一次讀取導致的問題了。
十一,Maven多模項目結構
以往的巨石項目大多是“一個項目,一個模塊,一個框架,一把梭”,項目結構大多是常見的J2EE那一套,src目錄下放源碼,webapp下方靜態資源以及未分離的前端頁面。
RuoYi的前后端分離的項目結構,首先將前后端分離,后端只提供接口的校驗以及數據的提供,前端提供頁面以及業務的交互,路由的控制。(雖然這讓后端開發的我無比狂喜,但是也讓全棧的我心在滴血,畢竟巨石項目的前端是比較簡單的,分離之后就意味着全新的技術棧)
下面這個表格列出了分包以及源碼的依賴度的優先級(越是底層,優先級越高),可以看出來,應用分包這塊是按照領域以及業務來區分的。
分包名稱
|
功能
|
優先級
|
備注
|
ruoyi-admin
|
web服務入口
|
1
|
通過功能參考具體實現
|
ruoyi-common
|
通用工具
|
3
|
|
ruoyi-framework
|
framework框架核心
|
2
|
通用性解決方案
|
ruoyi-generator
|
代碼生成
|
|
|
ruoyi-quartz
|
quartz定時任務
|
|
|
ruoyi-system
|
系統模塊:系統bo的CRUD
|
|
|