許可權控制2 0 重構,解耦頁面與字段

2021-10-08 16:14:01 字數 4045 閱讀 9811

在曲折中前進

1.許可權樹膨脹

隨著頁面增加,每個頁面都有大量字段,許可權樹會持續膨脹,而字段大多是相同的,只是分布在不同的模組和頁面中,每次新建角色或者上線新模組時勾選許可權的過程比較複雜,大多是重複勞動,而且也容易出現人為失誤。

目前已知確實出現了許可權的問題,因為線上許可權管控的原因,開發人員沒有許可權配置線上許可權,把相關資料交給產品去配置之後,開發和測試人員都沒有能力去驗證許可權配置的正確性,線上的許可權回歸也是交給產品完成。這部分過分依賴產品的個人可靠度,導致線上出現了產品配置錯誤導致許可權失效的問題。

2.時間複雜度過大

控制許可權方法的時間複雜度過大,理論上遍歷控制所有欄位的許可權時間複雜度應該是o(m*n),m是單個物件的字段數,n是返回的物件數量(此處不討論物件內巢狀的情況)。而實際的時間複雜度因為每次都需要遍歷一次許可權樹結構的原因,變成了o(m*n*i),i是許可權樹的葉子節點數,上一點也提到過,許可權樹在不斷膨脹,也會影響效能。

由於**實現不優雅導致的多餘的效能損耗雖然不明顯,但是作為乙個哪怕只有一點技術追求的開發,這都是應該盡量避免的。

1.產品邏輯優化

為了解決許可權樹膨脹的問題,通過與產品溝通想了個新的許可權解決方案:將頁面和字段的許可權分離。約定同乙個欄位的許可權只與人員身份相關,與具體的頁面無關,這樣就可以將頁面許可權和字段許可權拆分成兩顆許可權樹,頁面部分由前端維護,後端只需要關注字段許可權樹即可。好處是取消了頁面-欄位叉乘的組合,許可權樹節點會大量減少,同時可以將不同型別的字段按照型別進行分類勾選,不管是後端判斷許可權還是前台配置許可權都會方便不少。

不過也有缺點,就是前提要求過於嚴格,即許可權和頁面無關這個條件很難在專案裡做到,在我們的專案裡,因為字段等於指標,不同頁面的相同指標大部分時間只是維度不同的關係,所以可以達到這個條件。

2.實現邏輯優化

為了優化每次許可權控制的時間複雜度,首先是需要把每次遍歷許可權樹的步驟取消掉,可以提前遍歷生成乙個需要過濾許可權的字段key組成的set。其次是考慮是否需要遍歷所有的物件的每個字段,其實仔細想想是不需要的,只需要找到這個物件中需要受到許可權控制的字段,然後就可以批量處理。對於多層物件巢狀的情況可能在邏輯理解上會稍微麻煩一點,但是其實**上也只是在原先遞迴的基礎上增加一點而已。這樣就把時間複雜度裡的m從物件的字段數變為了需要控制許可權的字段數。最終的時間複雜度是o(m*n)

其實還有乙個改動,是實現上不夠優雅的地方。之前的寫法針對的可能只是簡單的list,而實際的物件可能存在多層的巢狀,需要將這些巢狀邏輯統一一下,以及對入口方法的包裝,簡化api等,都期望在這個版本裡能夠完成。

1 許可權樹處理:

public static setgetnopermissionkeyset(listchildren) 

linkedlistsourcetrees = new linkedlist<>(children);

hashsetnopermissionset = sets.newhashset();

while (sourcetrees.size() != 0)

if (collectionutils.isnotempty(treenode.getchildren())) }}

return nopermissionset;

}

將原本返回boolean值的方法替換成了返回乙個無許可權欄位的set。在判斷某個字段是否是受許可權控制時,不再需要每次都遍歷許可權樹去找這個字段,只需要通過set.contains方法來判斷,執行的時間複雜度是o(1),這也得益於葉子節點的唯一性,以前的方案裡,唯一性通過拼裝模組-方法-欄位來保證,改造過後的結構可以直接認為唯一。

在這種方案下欄位許可權依舊是樹結構的理由是需要進行分類勾選,分類才是減少前台操作量和失誤可能性的最佳選擇。通過對欄位節點的分類,又一次減少了配置時的勾選操作量,維護了字段組之間的對映關係。只有簡單易用才能降低人為操作失誤的可能。

2 包裝方法入口,遞迴邏輯優化

控制入口,如果是list結構,會遞迴呼叫這個方法,如果是普通的物件會自動進入過載的方法,判斷物件裡字段的許可權。

public static void checkmetricauthorityrefactor(listobjlist, setnopermissionset) 

for (t obj : objlist)

}

對於外部呼叫的方法,為了避免直接呼叫內部那個過載的方法,可以統一包裝成list。為什麼不推薦呼叫裡面的checkmetricauthorityrefactor方法。這樣做的原因,一是為了統一入口,避免呼叫之前還要判斷一次型別;二是考慮到內部的這個方法其實可以和外部的合併成乙個方法。

具體的內部實現是通過反射獲取字段資訊,然後通過欄位名稱判斷是否受到許可權控制(這裡封裝一層的原因是會出現一些派生的字段,在基礎欄位名的前提下,含有一些字首或字尾)。之後再通過反射,呼叫欄位的get方法賦值為null

方法裡比較重要的是要對字段型別進行判斷,檢查是否是基礎字段,如果是list型別的字段,會返回到過載的方法裡重新進行型別的判斷。

這兩個方法是可以合在一起的,寫的時候為了梳理邏輯給拆開了,所以直觀上看起來覺得單次執行在兩個方法間跳來跳去,其實是內外層級的關係。

private static void checkmetricauthorityrefactor(t obj, setnopermisssionset) 

field fields = obj.getclass().getdeclaredfields();

for (field field : fields) catch (nosuchmethodexception | illegalacces***ception | invocationtargetexception e)

}} else catch (illegalacces***ception e) }}

}

其實這個方法還有優化的地方,因為使用反射呼叫getset方法檢視和賦值看著就是很繁瑣的操作,所以在知道欄位名稱的情況下,可以直接修改可見性,然後修改值。

private static void checkmetricauthorityrefactor(t obj, setnopermisssionset) 

field fields = obj.getclass().getdeclaredfields();

for (field field : fields) catch (illegalacces***ception e)

}} else catch (illegalacces***ception e) }}

}

這裡出現了乙個新的值得思考的問題,一旦使用setaccessible對字段的可見性進行了修改,會帶來什麼樣的負面後果?

此處場景下僅對返回的vo可見性進行了修改,個人感覺負面影響有限,不太需要擔心。

3 todo

由於返回結構中有可能存在多層巢狀,迴圈又是以單個物件為主體進行的,所以沒有辦法統一處理,只能按照物件逐層獲取,導致最終目標,縮小m*n中的m這個想法沒有實現。

雖然這篇許可權控制2.0的名字叫方案重構,其實在**層面上的改動並不多,主要是產品邏輯的重構,後端的實現只是在原來的基礎上搭車做了一些優化,畢竟線上用得好好的本來是沒有機會做這種改動的。

這也是做產品的乙個問題吧,有時候因為時間趕或者想法不周全寫出來的不太優雅的**沒有機會改動,畢竟改動後功能需要測試回歸,本來就緊張的排期再加上一些歷史問題改動的回歸就很捉襟見肘了。

最終這個方案因為產品的原因還是擱淺了,沒能上線。很難受,倒不是因為**寫了沒上,而是我花了整整一天的時間去梳理之前一堆雜亂的許可權。而且這種無效開發完全能夠避免。

現在這段**還處在唄注釋掉的階段,說不好哪天就會被當作無用**刪掉,在它完全消失之前,記錄下來,也算是我那兩天的勞動稍微有了點價值。

0 重構概述

這一系列的重構知識總結自馬丁福勒的 重構 改善既有 的設計 一書。為什麼要重構?因為乙個專案往往不只乙個人在寫,其他人也會來讀寫你的 有些人甚至自己寫的 幾個月之後就看不懂當初自己寫的是什麼了。重構的目的 改進軟體的設計,使軟體更易理解,容易找出bug,在後期要新增新功能時,提高程式設計速度,重構後...

NO1重構感想

經過了長達乙個月的編碼,終於完成了no1的重構開發,雖然還有一些隱含的邏輯上的一些問題。經過這次開發,讓我感觸頗多。首先,我們一定要善於學習。大家都知道學習的好處,但是在實踐中都得到這個結論和聽別人說完全是兩碼事情。舉個例子,假如說,我想要獲得乙個標籤的innerhtml屬性,以前我都是這麼寫的。a...

4 3 重構查詢方式

設計查詢的時候乙個需要考慮的重要問題是,是否需要將乙個複雜的查詢分成許多簡單的查詢。mysql內部每秒能夠掃瞄記憶體中上百萬行資料,相比之下,mysql響應資料給客戶端就慢的多了。所以,有時候將乙個大的查詢分解為多個小查詢是有必要的。4.3.1 切分查詢 乙個大查詢如果一次性執行的話,可能一次鎖住很...