如何快速搭建一個(gè)管理后臺(tái)-權(quán)限管理

0. 系統(tǒng)設(shè)計(jì)
1. 數(shù)據(jù)管理
2. 身份認(rèn)證
3. 權(quán)限管理

身份認(rèn)證處理的是 “你是誰(shuí)的問(wèn)題”,而權(quán)限管理處理的是 “你能干什么的問(wèn)題”?!澳隳芨墒裁吹膯?wèn)題”這句話里有兩個(gè)關(guān)鍵的點(diǎn):一個(gè)是 “你”,“你” 是用戶;另一個(gè)是“干什么”,“干什么” 是 處理,在 WEB 系統(tǒng)中所有的處理都是由客戶端對(duì)服務(wù)器發(fā)起的 HTTP資源請(qǐng)求。對(duì)于 http 資源請(qǐng)求一般有能力請(qǐng)求和沒(méi)有能力請(qǐng)求,對(duì)應(yīng)于 http 狀態(tài)碼則是200、403。在有 http 資源訪問(wèn)權(quán)限的基礎(chǔ)上一般對(duì)于該請(qǐng)求所返回的 數(shù)據(jù)資源 也會(huì)有不同程度的限制,例如作者和管理員能看到和處理的文章數(shù)量不同。

把以上這HTTP資源和數(shù)據(jù)資源都抽象成系統(tǒng)資源,那么權(quán)限管理就簡(jiǎn)化成了系統(tǒng)中用戶和系統(tǒng)資源的關(guān)系映射。

沒(méi)圖

用戶分組

在公司里具有相同職責(zé)的人在系統(tǒng)中相應(yīng)會(huì)有相同的權(quán)限,而不同的職責(zé)又跟公司的組織架構(gòu)有關(guān),所以很多時(shí)候設(shè)計(jì)權(quán)限管理的時(shí)候會(huì)吧公司組織結(jié)構(gòu)也設(shè)計(jì)進(jìn)來(lái)。事實(shí)上用相同能力和公司成員做映射的話完全可以把公司組織結(jié)構(gòu)給扔掉,這種映射也就是 角色

利用以上分析現(xiàn)在可以做這么一個(gè)映射關(guān)系:用戶-->角色-->系統(tǒng)資源。即通過(guò)給用戶授予角色來(lái)實(shí)現(xiàn)用戶->資源的映射過(guò)程。

在角色的處理上一般有單一角色和多角色兩種方案,同時(shí)在這兩種方案上對(duì)能單獨(dú)對(duì)用戶增減權(quán)限還要有額外的設(shè)計(jì)。

在公司里通常是一個(gè)蘿卜一個(gè)坑,即一個(gè)人只有一種角色,但是實(shí)際過(guò)程中身兼多職的事情常常發(fā)生,所以一般我做權(quán)限設(shè)計(jì)底層都是多角色。

資源管理

如開(kāi)頭所說(shuō),把 “HTTP” 資源和系統(tǒng)中的 “數(shù)據(jù)資源” 都抽象成 “系統(tǒng)資源” 來(lái)處理,在系統(tǒng)中進(jìn)行檢查權(quán)限檢查就可以變成 “是否擁有” 這么簡(jiǎn)單的事情了。到數(shù)據(jù)層面,用戶擁有哪些權(quán)限也只是一維的數(shù)據(jù),到這一步怎么存儲(chǔ)就隨意了。[我初次設(shè)計(jì)的時(shí)候是用關(guān)聯(lián)表進(jìn)行存儲(chǔ)的,后來(lái)聽(tīng)從別人的建議并產(chǎn)考 sentry 權(quán)限控制組件,采用的 json 字符串方式進(jìn)行存儲(chǔ)。]

根據(jù)這些現(xiàn)在權(quán)限抽象處理和權(quán)限持久化差不多都解決了,但是這里會(huì)有第二個(gè)問(wèn)題:如何管理這些權(quán)限?

對(duì)于一個(gè)小系統(tǒng),十幾二十幾個(gè)功能的系統(tǒng)可能只需做個(gè)小表格把這些權(quán)限都列舉一下,管理員根據(jù)角色去勾選一下就好,但是對(duì)于功能繁多的系統(tǒng)這樣明顯是行不通的,誰(shuí)會(huì)去在幾十上百個(gè)都差不多的名稱里去找到你需要的那個(gè)權(quán)限名稱去!

所以這些權(quán)限如何呈現(xiàn)給用戶讓他們自己去管理這又是一個(gè)問(wèn)題!

呈現(xiàn)方式

這里先回顧一下后臺(tái)菜單的排布方式,大概有一下幾種

1. 功能簡(jiǎn)單,只要有一級(jí)導(dǎo)航就夠了(圖片來(lái)自element.eleme.io
2. 功能稍微復(fù)雜,有二級(jí)導(dǎo)航(圖片來(lái)自element.eleme.io)
3. 更為復(fù)雜的系統(tǒng),僅二級(jí)菜單無(wú)法滿足系統(tǒng)功能(圖片來(lái)自element.eleme.io)

1和2的形式都比較簡(jiǎn)單,基本沒(méi)什么可說(shuō)的。就直接說(shuō)三級(jí)導(dǎo)航的這種后臺(tái)怎么處理,然后一級(jí)二級(jí)這種的基本都不是問(wèn)題。

處理三級(jí)導(dǎo)航系統(tǒng)的后臺(tái)時(shí)我一般遵循這么一個(gè)原則:

  • 一級(jí):以業(yè)務(wù)劃分,相同業(yè)務(wù)線的在同一個(gè)一級(jí)導(dǎo)航下,同時(shí)在代碼層面也進(jìn)行命名空間的分割
  • 二級(jí):以功能相關(guān)性劃分,相似或有關(guān)聯(lián)的功能化為一組。我通常二級(jí)導(dǎo)航不負(fù)責(zé)真正的功能只是對(duì)三級(jí)菜單的分組合并
  • 三級(jí):這一級(jí)其實(shí)已經(jīng)是具體的功能頁(yè)了。具體到權(quán)限管理時(shí)會(huì)有菜單,頁(yè)面,功能等區(qū)別
  • 處理三級(jí)導(dǎo)航的后臺(tái)除了圖三所示的那種所有導(dǎo)航都在左邊,其實(shí)還可以一級(jí)導(dǎo)航在頂部,二三級(jí)在左邊

在處理只到一級(jí)或者二級(jí)菜單的系統(tǒng)時(shí),菜單是否展示往往依靠硬編碼就解決了,因?yàn)檫@時(shí)候功能少,設(shè)計(jì)復(fù)雜了反而影響修改的靈活性,但是對(duì)于三級(jí)菜單的系統(tǒng)來(lái)講要權(quán)限和菜單同步僅靠硬編碼就很困難了,要做到足夠的靈活可配置就需要設(shè)定一個(gè)規(guī)則進(jìn)行約定了。

數(shù)據(jù)結(jié)構(gòu)

先寫(xiě)一個(gè)三級(jí)菜單的菜單數(shù)據(jù)結(jié)構(gòu)

[
    {
        "name":"一級(jí)導(dǎo)航1",
        "url": "/nav1",
        "chirdren":[
            {
                "name":"二級(jí)導(dǎo)航1", // 一般為菜單分組,只提供折疊功能而不提供導(dǎo)航到頁(yè)面的功能
                "url": "/nav1/nav2-1",
                "chirdren": [
                    {   // 這一級(jí)才是真正的菜單
                        "name":"三級(jí)導(dǎo)航1",
                        "url":"/nav1/nav2-1/path1",
                    },
                    { 
                        "name":"三級(jí)導(dǎo)航2",
                        "url":"/nav1/nav2-1/path2",
                    },
                ]
            },
            {
                "name":"二級(jí)導(dǎo)航2",
                "url": "/nav1/nav2-2",
                "chirdren": [
                    {
                        "name":"三級(jí)導(dǎo)航1",
                        "url":"/nav1/nav2-2/path1",
                    },
                    { 
                        "name":"三級(jí)導(dǎo)航2",
                        "url":"/nav1/nav2-2/path2",
                    },
                ]
            }
        ]
    },{
        ...
    }
]

上面一級(jí)導(dǎo)航和二級(jí)導(dǎo)航都有一個(gè) URL,實(shí)際上二級(jí)作為分組只有折疊的作用,一級(jí)導(dǎo)航如果在頂部,那個(gè) URL 其實(shí)點(diǎn)擊后跳轉(zhuǎn)到的也是該分組下的一個(gè)三級(jí)導(dǎo)航。

另外這個(gè)數(shù)據(jù)結(jié)構(gòu)只處理了一級(jí)導(dǎo)航>二級(jí)導(dǎo)航>三級(jí)導(dǎo)航的數(shù)據(jù)結(jié)構(gòu),三級(jí)導(dǎo)航是列表頁(yè)的情況下時(shí)該三級(jí)導(dǎo)航可能還會(huì)關(guān)聯(lián) "新建頁(yè)面[GET]","新建提交[操作|POST]","編輯頁(yè)面[GET]","編輯提交[操作|PUT]","刪除提交[操作|DELETE]", 這些項(xiàng)雖然不在菜單中展示,但是在權(quán)限管理頁(yè)面這些卻是必須的。

  • 從這一段開(kāi)始我不再用一級(jí)導(dǎo)航、二級(jí)導(dǎo)航、三級(jí)導(dǎo)航這樣的名稱,而是用導(dǎo)航>分組>菜單這樣功能比較明確的名稱

上一節(jié)【呈現(xiàn)形式】有提到如何管理但是并沒(méi)有真正解決三級(jí)菜單下的如何呈現(xiàn)的問(wèn)題?,F(xiàn)在提問(wèn):如果用和導(dǎo)航布局一樣的層現(xiàn)方式來(lái)呈現(xiàn)權(quán)限管理是否可行呢?

這里還有另外一個(gè)問(wèn)題,就是當(dāng)我們打開(kāi)菜單下的一個(gè)編輯頁(yè)面不管我刷不刷新瀏覽器改頁(yè)面上層的菜單和導(dǎo)航應(yīng)該都是高亮的,這又該如何解決?

下面給一個(gè)我現(xiàn)在使用的權(quán)限管理數(shù)據(jù)結(jié)構(gòu)

[
    {
        "name":"導(dǎo)航1",
        "index": "welcome",
        "groups": {
            "分組1": [
              "home.dashboard.index1",
              "home.dashboard.index2",
            ],
            "分組2": [
              "home.dashboard.index3",
            ],
        },
        "routes":{
            "welcome":{
                "name":"二級(jí)導(dǎo)航1", 
                "url": "/nav1/nav2-1",
            },
            "home.dashboard.index1": {
                "name":"菜單",
                "url": "/home/dashboard/index1",
                "type":"menu"
            },
            "home.dashboard.index1-1": {
                "name":"頁(yè)面1-1",
                "url": "/home/dashboard/index1-1",
                "type": "page",
                "refer": "home.dashboard.index1",
            },
            "home.dashboard.index1-1": {
                "name":"頁(yè)面1-2",
                "url": "/home/dashboard/index1-2",
                "type": "page",
                "refer": "home.dashboard.index1",
            },

            "home.dashboard.index2": {
                "name":"菜單",
                "url": "/home/dashboard/index2",
                "type":"menu"
            },
            "home.dashboard.index2-1": {
                "name":"頁(yè)面3-1",
                "url": "/home/dashboard/index2-1",
                "type": "page",
                "refer": "home.dashboard.index2",
            },
            "home.dashboard.index2-1": {
                "name":"頁(yè)面2-2",
                "url": "/home/dashboard/index2-2",
                "type": "page",
                "refer": "home.dashboard.index2",
            },

            "home.dashboard.index3": {
                "name":"菜單",
                "url": "/home/dashboard/index3",
                "type":"menu"
            },
            "home.dashboard.index3-1": {
                "name":"頁(yè)面3-1",
                "url": "/home/dashboard/index3-1",
                "type": "page",
                "refer": "home.dashboard.index3",
            },
            "home.dashboard.index3-1": {
                "name":"頁(yè)面3-2",
                "url": "/home/dashboard/index3-2",
                "type": "page",
                "refer": "home.dashboard.index3",
            },
        }
    },{
        ...
    }
]
  • 因?yàn)橛?Request Method所以在 http 請(qǐng)求中一個(gè) uri 并不能確定唯一一個(gè)請(qǐng)求,二者加一起才能唯一確定一個(gè)請(qǐng)求。而laravel框架剛好提供了路由別名的方案,即你可以給一個(gè)任意一個(gè)請(qǐng)求起一個(gè)別名,并且一個(gè)別名只能對(duì)應(yīng)唯一一個(gè) http request。我們可以根據(jù)這個(gè)別名生成 url,也能根據(jù) Request 實(shí)例獲得該實(shí)例的別名。關(guān)于別名的另一個(gè)好處就是只要我起名的方案不變 url 需要改變是不用擔(dān)心的。所以在實(shí)際應(yīng)用中我也是用別名設(shè)計(jì)的配置文件。

事實(shí)上在實(shí)際應(yīng)用中我的配置文件路由節(jié)點(diǎn)長(zhǎng)的是這個(gè)樣子的

        'works'=>[
            'name'  =>'工作記錄',
            'uri'   =>'/system/work',  'method'=>'get',
            'uses'  =>'HomeController@work',
            'limit-on'=>false,
            'log.file'   => '【{{user.name}}】訪問(wèn)了操作日志頁(yè)',
            'throttle'=>100,
        ],

因?yàn)楦鷦e名關(guān)聯(lián)的除了路由,還有行為日志、訪問(wèn)限制、權(quán)限管理等行為,所以直接設(shè)計(jì)在一塊好了,一個(gè)配置文件搞定一切,這樣當(dāng)配置項(xiàng)增加的時(shí)候也不用擔(dān)心顧此失彼的事情了。

單頁(yè)面應(yīng)用下的權(quán)限控制

單頁(yè)面應(yīng)用前后端是完全分離的狀態(tài)的,前端路由和后端API路由沒(méi)有必然的聯(lián)系。像前端 /#/post/1234這樣的路由調(diào)用后端接口則可能為post?id=1234這樣的形式,二者的關(guān)聯(lián)只在業(yè)務(wù)邏輯代碼的 ajax 請(qǐng)求那塊,一般情況下是靠前后端程序員約定。而且前端路由相對(duì)會(huì)比后端少,因?yàn)榍岸说囊粋€(gè)功能復(fù)雜的頁(yè)面會(huì)對(duì)應(yīng)后端許多接口。所以前后端的權(quán)限怎么控制?

原則上可以制定一套規(guī)則同時(shí)適應(yīng)于前后端路由和權(quán)限控制,但由于要考慮前后端權(quán)限控制和路由的問(wèn)題以及一些未定義的場(chǎng)景這個(gè)方案將會(huì)很復(fù)雜。而且如果是團(tuán)隊(duì)協(xié)同開(kāi)發(fā),那么前后端成員都必須在了解這個(gè)方案和命名體系的前提下才能愉快的協(xié)作。事實(shí)上多人協(xié)作開(kāi)發(fā)的過(guò)程中最大的問(wèn)題就是知識(shí)同步。

另一個(gè)問(wèn)題,在上一大節(jié)中我們可以了解到權(quán)限控制可以簡(jiǎn)化成一個(gè)類(lèi)似查 hash 表的情景,那么這個(gè) hash 表誰(shuí)來(lái)維護(hù),前端?后端?還是前后端同時(shí)維護(hù)一份。在這里面任意一種方案都有一個(gè)不可逃避的問(wèn)題就是配置同步,1.如果前端維護(hù)那么這個(gè)hash 表要同步給后端,2.如果后端維護(hù)要同步給前端,3. 如果同時(shí)維護(hù),那就要保持同步修改。 一般來(lái)講(1.)的實(shí)現(xiàn)會(huì)更困難,(2.)實(shí)現(xiàn)的前提是前端必須在后端服務(wù)的基礎(chǔ)上進(jìn)行開(kāi)發(fā),而且很大層面上是在前后端用一個(gè)方案這個(gè)前提下。方案(3.)相對(duì)來(lái)講權(quán)限表的維護(hù)成本更高一點(diǎn),但其實(shí)這個(gè)維護(hù)也只是在設(shè)計(jì)之初前后端雙方同時(shí)維護(hù)而已,產(chǎn)品成型之后的修改遠(yuǎn)沒(méi)剛開(kāi)始的修改更頻繁,而且方案(3.)可以解開(kāi)上一段所描述的知識(shí)同步的問(wèn)題,因?yàn)橹灰s定某個(gè) key 是控制某個(gè)功能,雙方根據(jù)這個(gè)約定去開(kāi)發(fā)各自的功能基本不會(huì)有什么大問(wèn)題,同時(shí)各自路由的命名方案也可以由此解開(kāi)。

github: https://github.com/chen-wen/admin
github: https://github.com/chen-wen/vue-spa

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,697評(píng)論 19 139
  • 當(dāng)我還是認(rèn)為自己是個(gè)孩子的時(shí)候,你來(lái)到了這個(gè)世界,來(lái)到了我身邊,我有些措手不及。但也得面對(duì)你-兒子。 漸漸的,你學(xué)...
    健康倫閱讀 324評(píng)論 0 0
  • 1 “嗨,小姐,請(qǐng)問(wèn)您想吃什么?” “Ah,我,不知道,可以再等我一會(huì)兒?jiǎn)???-三個(gè)小時(shí)后- “抱歉,小姐,您還...
    森森_line閱讀 491評(píng)論 3 3

友情鏈接更多精彩內(nèi)容