一、權(quán)限系統(tǒng)這一天將講述一個基本的基于數(shù)據(jù)庫的權(quán)限管理系統(tǒng)的設(shè)計,在這一天的課程的最后將講述“左右值無限分類實現(xiàn)算法”如何來優(yōu)化“系統(tǒng)菜單”的結(jié)構(gòu)而告終。今天的內(nèi)容和前幾天的基礎(chǔ)框架是一樣的它們都屬于基礎(chǔ)知識,在這些基礎(chǔ)知識上還可以擴(kuò)展出無數(shù)的變種與進(jìn)化設(shè)計。 二、先來看客戶的一個需求2.1 用戶實際需求1. 所有的用戶、角色可動態(tài)配置 2. 所有的系統(tǒng)菜單的權(quán)限要求具體到“增,刪,改、查、打印、導(dǎo)出”這樣的小權(quán)限的設(shè)計 3. 所有的權(quán)限基于角色來進(jìn)行劃分和判斷 4. 一個用戶可能屬于多個角色 5. 系統(tǒng)菜單也能夠動態(tài)的“增、刪、改、查” 2.2 系統(tǒng)權(quán)限菜單樣例
三、基于數(shù)據(jù)庫的系統(tǒng)權(quán)限表設(shè)計3.1 ER(Entity Relationship)圖圍繞上述需求,我們可以在數(shù)據(jù)庫內(nèi)進(jìn)行如下的表設(shè)計,下面直接給出ER圖:
3.2 表關(guān)系詳解上述設(shè)計有6張表,其中: T_User表 用于存放用戶信息,此處只存放基礎(chǔ)信息
T_Role表 用于存放系統(tǒng)角色信息
T_User_Role表 用于存放系統(tǒng)用戶與角色的匹配關(guān)系
T_Sys_Menu表 這張就是用于存放系統(tǒng)菜單的表了,這張表的設(shè)計主要使用了如下的表設(shè)計技巧:
注意這邊的MENU_ID與MENU_PID 如果這個菜單項是一級菜單,那么我們把它的MENU_PID設(shè)為0 如果這個菜單是另一個菜單的子菜單,那么我們就把它的MENU_PID設(shè)為它的父菜單的MENU_ID。 有了這樣的結(jié)構(gòu),我們一個遞歸就能把這顆“樹”顯示出來了,是不是? 此處以O(shè)racle數(shù)據(jù)庫為例,不使用遞歸,直接把樹形結(jié)構(gòu)在數(shù)據(jù)庫中就造型造好(當(dāng)然,還有更好的方法如:有人喜歡設(shè)level或者是deep這樣的字段來簡化程序解析樹型結(jié)構(gòu)菜單,稍后我們會來講一個根本不需要用遞歸的樹型菜單的設(shè)計來最大程度優(yōu)化設(shè)計。) 顯示整顆樹型菜單結(jié)構(gòu)的Oracle語句:
上述語句,已經(jīng)用數(shù)據(jù)查詢用句就將我們的這個“樹”的層次關(guān)系理出來了,如果我們手上有一個控件叫dtree.js,那么一個循環(huán)就可以把這個樹顯示出來了,不是嗎? 來看dtree.js的應(yīng)用
大家看上面,這個是dtree.js插件,一個專門用于生成樹的js插件的使用方法,那么如果我們附以上述的sql語句在數(shù)據(jù)庫中把數(shù)據(jù)選出來后,是不是只要一個循環(huán)就可以給這個dtree.js插件顯示了,不是嗎? 我們?nèi)绻幌腼@示整顆樹只想顯示如:
只顯示系統(tǒng)管理菜單有其下列所有的子菜單,那么我們的Oracle中的Sql應(yīng)該怎么寫呢? 經(jīng)查“系統(tǒng)管理”這個菜單的MENU_ID=’105’,于是我們的Sql語句如下:
對吧?很簡單哈! T_Privilege表 用于存放系統(tǒng)每個菜單的詳細(xì)子權(quán)限如“增,刪,改,查”
T_Menu_Privilege表 這張表就是我們的最終終結(jié)大BOSS表,它里面是一個完整的系統(tǒng)權(quán)限與角色關(guān)系間的對應(yīng)。 比如說: 我們想要知道“user”這個角色,可以操作哪些菜單,哪些權(quán)限,那么我們的SQL語句如下:
![]() 通過這個結(jié)果我們就知道了 1. 角色“user”能操作哪些菜單 2. 角色“user”對某個菜單具有什么樣的權(quán)限
進(jìn)而,我們可以推出: 我們想要知道Danzel這個人,可以操作哪些菜單,以及在哪些菜單上有哪些可供操作的權(quán)限,我們使用如下的SQL語句:
通過這個結(jié)果我們就知道了 1. Danzel這個人能操作哪些菜單 2. Danzel對某個菜單具有什么樣的權(quán)限 3.3 界面制作關(guān)于jsp,什么dao層,service層的具體代碼這個就不講了,這個沒有意義的哦,我們來講設(shè)計。 登錄后如何顯示左邊的樹型菜單: ü 取得用戶名 ü 將該用戶名作為參數(shù)input進(jìn)如下的SQL語句得到該用戶在登錄后可以看到的系統(tǒng)菜單:
將該結(jié)果直接給于index.jsp頁面上的dtree.js組件,一個循環(huán),所有菜單曾樹形顯示。 知道用戶登錄后能夠?qū)δ男┎藛?,并且在相關(guān)界面操作時有哪些子權(quán)限如:增、刪、改、查、打印、報表的設(shè)計: ü 在登錄時得到用戶名等信息,然后將該用戶名作為參數(shù)input進(jìn)入如下的sql語句:
ü 得到上述結(jié)果后,使用:Haspmap<String menuId, List menuList>這樣的結(jié)構(gòu)將該用戶所屬的角色分對每個菜單有哪些操作(增、刪、改、查、打印、報表)進(jìn)行存儲,放入用戶的session中,在以后用戶在每個界面進(jìn)行點擊動作時進(jìn)行判斷,或者可以寫個filter來進(jìn)行判斷,是不是就可以作到: 知道該登錄用戶在登錄后可以對哪些菜單進(jìn)行操作,并且擁有什么操作權(quán)限啦?
相應(yīng)的我們還需要制作如下的界面: ü 用戶的管理界面 ü 角色的管理界面 ü 用戶與角色的分配界面 ü 系統(tǒng)菜單的管理界面 ü 具體權(quán)限項的管理界面 ü 系統(tǒng)菜單與角色間具體的權(quán)限分配界面 好了,有了這些界面,一個完整的基于數(shù)據(jù)庫引擎的權(quán)限系統(tǒng)算是完成了。 嚴(yán)重注意: 在制作“系統(tǒng)菜單與角色間具體的權(quán)限分配界面”時,如果在界面上把某個角色對該條菜單的“查看”這個選項disable后,那么該角色將不擁有任何該菜單的所有權(quán)限,舉例: 某角色對菜單A擁有如下權(quán)限: 增、刪、改、打印 但是這個“查看”權(quán)限沒有,也有可能是管理員誤操作,但是從真實情況我們來說,這個角色連“查看”的權(quán)限都沒有,連菜單都進(jìn)不了,他能做什么“增、刪、改。。?!钡绕渌牟僮靼??操作個頭啊!是吧? 所以一旦界面上該角色對某個系統(tǒng)菜單沒有了查看權(quán)限后,它對這個菜單的其它權(quán)限也必須從T_MENU_PRIVILEGE這個表中刪除。 四、改進(jìn)T_SYSTEM_MENU表的設(shè)計前面我們用的是Oracle特有的遞歸SQL將樹形菜單在從數(shù)據(jù)庫中選取出來時就已經(jīng)是一顆樹的結(jié)構(gòu)了,但是像MYSQL,SQL SERVER, DB2等可能不帶有這樣的特SQL,那就需我們自己動手去寫遞歸,還有就是很多工程用的是jquery的tree或者是其它相關(guān)的ajax tree,這些tree都需要用到一個字段叫l(wèi)evel(此處指深度、層次的意思),如果按照原來的表結(jié)構(gòu),要取得這個level,恐怕是要寫遞歸算法了。就算有些數(shù)據(jù)庫有類似的語句,那也需要你去修改你的SQL語句從未影響了性能與通用性。 我們在這邊說,我們無論什么數(shù)據(jù)庫,如果都用相同的SQL就能把我們需要的東西在數(shù)據(jù)庫中就排好樹形結(jié)構(gòu)然后一次性選取出來,那應(yīng)該有多好啊。答案是有的,在原來的T_SYSTEM_MENU表中改動也不大,只需要增加兩個字段即可,即:lft與rgt(left, right),這種設(shè)計其實已經(jīng)有了,我在此只不過結(jié)合實際例子把它應(yīng)用到實際上,并且進(jìn)一步詳細(xì)描述如果來實現(xiàn)它,它就是被稱為: 左右值無限分類實現(xiàn)算法也稱為預(yù)排序遍歷樹算法,對于這種層次型數(shù)據(jù)(Hierarchical Data)一般我們有兩種設(shè)計方法: ü 毗鄰目錄模式(adjacencylist model) ü 預(yù)排序遍歷樹算法(modifiedpreorder tree traversal algorithm) 4.1 基于lft, rgt的無限分類算法我們來看一個圖,下面我們把我們原有的菜單畫成下面這樣的層次關(guān)系: ![]() 我們把原有的系統(tǒng)菜單畫成了一個個的橢圓,最外層的就是我們的菜單,然后在每個橢園的兩個端點即left與right,按照從左->右,開始用數(shù)字來標(biāo)號,上面這個圖中可以看到最外層這個大橢園的lft(左)為1,它的rgt(右)為24。 那么我們可以用一條標(biāo)準(zhǔn)的SQL,而非什么數(shù)據(jù)庫自帶的特有的、特殊的SQL來顯示出這個樹形菜單,來看下面的SQL:
來看顯示的結(jié)果
看看上面這個結(jié)果,怎么樣? ü 樹形結(jié)構(gòu)也有了(可以用于dtree來顯示); ü 層次level也有了(可以用于ajax的一些tree); ü 我們用的SQL又是最標(biāo)準(zhǔn)的所有的數(shù)據(jù)庫都能用到的SQL; 嘗到甜頭了是吧?那我們下面來看如何對這樣的基于t, rgt的數(shù)據(jù)結(jié)構(gòu)來作插入操作? 4.2 如何在現(xiàn)有節(jié)點中插入新的子節(jié)點如果現(xiàn)在我們要在“報表查詢”這個圓里加入一個菜單,假設(shè)我們就叫“周報”,那么再來看這個原有的圖發(fā)生了什么樣的改變,來看: ![]() 看到么,原有的最外層橢園的rgt+2,原有的報表查詢這個園的右邊界呢?是不是也加了2啊?而原有的“月報”這個圓的lft加了多少?也是+2! 那么來看“周報”這個圓的lft與rgt關(guān)系: “周報”的lft= “報表查詢”這個圓的lft+1 “周報”的rgt=“報表查詢”這個圓的lft+2 于是我們就可以整理出在原有葉子中插入child的公式: 第一步:選取要被插入new child的外面這個圓的lft的值 第二步:原有的數(shù)據(jù)中所有的rgt如果>第一步中得到的lft的值,那么全部+2 第三步:原有的數(shù)據(jù)中所有的lft如果>第一步中得到的lft的值,那么全部+2 第四步:將插入的節(jié)點的lft與rgt的設(shè)計,新節(jié)點的lft =第一步中的lft+1,新節(jié)點的rgt=第一步中 的lft+2 來看一個具體的例子: 我們要在“報表查詢”即menu_id=’101’ 中插入一個新的菜單,叫“周報”,下面是按照上面四步算法的相關(guān)SQL語句:
第一步
這一步我們得到的值為:2 第二步:
第三步:
第四步:
插完后我們運行查詢SQL:
Look, 數(shù)據(jù)正確無誤,我們來看整個t_sys_menu表里的數(shù)據(jù):
Look,整個最外層的“圓”,右邊界增加了2,從原有的24變成了26。 1.3 如何插入一個新的節(jié)點上面講的是在原有的節(jié)點中插入一個子節(jié)點,現(xiàn)在來講,如何插入一個新的節(jié)點,比如說: 我們現(xiàn)在有:報表查詢,系統(tǒng)管理兩大菜單,我們還想加一個菜單:保單審核,怎么來做? 我們把4.2節(jié)中“如何在現(xiàn)有節(jié)點中插入新的子節(jié)點”里四步公式,稍稍改動一下 第一步:選取要被插入新的節(jié)點左邊節(jié)點的rgt的值 第二步:原有的數(shù)據(jù)中所有的rgt如果>第一步中得到的rgt的值,那么全部+2 第三步:原有的數(shù)據(jù)中所有的lft如果>第一步中得到的rgt的值,那么全部+2 第四步:將插入的節(jié)點的lft與rgt的設(shè)計,新節(jié)點的lft =第一步中的rgt+1,新節(jié)點的rgt=第一步中 的rgt+2 下面來看我們在“報表查詢”與“系統(tǒng)管理”中間,插入一個菜單叫“保單審核”。 第一步
這一步我們得到的值為:11 第二步:
第三步:
第四步:
運行下面的SQL語句我們來檢查一下插入的效果:
怎么樣,結(jié)果對了吧,呵呵! 看看整個菜單的右邊界吧,從原來的26變成了28了,是不是哦? 1.3 如何刪除一個節(jié)點來看公式 第一步:選取要被刪除的菜單的lft的值,rgt的值,以及寬度(width=rgt-lft+1); 第二步:刪除所有的位于第一步中得到的lft與rgt之間的節(jié)點; 第三步:將所有的右邊界大于第一步中得到的rgt的所有節(jié)點的rgt的值減去第一步中得到的width 第四步:將所有的左邊界大于第一步中得到的rgt的所有節(jié)點的lft的值減去第一步中得到的width 來看實際例子,我們有下面這樣的數(shù)據(jù):
我們想將menu_id=114的保單審核刪除,當(dāng)然,這是一個父節(jié)點,如果把它刪了,其子節(jié)點115即手工審核也必須被一起刪除,要不然它就成為臟數(shù)據(jù)了是不是?套用上述四步公式: 第一步:
第二步:
第三步:
第四部:
全部步驟完成后,我們來運行檢驗的SQL:
結(jié)果正確,再來看整個“菜單”的邊界,從原來的28縮減成了26了,結(jié)果正確。 上述這種基于lft, rgt左右值無限分類實現(xiàn)算法的個菜單的好處在于: ü SQL語句不受特定的數(shù)據(jù)庫的限制 ü SQL語句通用 ü 直接從數(shù)據(jù)庫中遠(yuǎn)取出來的結(jié)構(gòu)化的數(shù)據(jù)即可滿足需要pid的如:dtree.js這樣的JS控件的需要也可以滿足需要level的ajax tree控件的需要。
|
|
|