如何快速搭建一个管理后台-权限管理

0. 系统设计
1. 数据管理
2. 身份认证
3. 权限管理

身份认证处理的是 “你是谁的问题”,而权限管理处理的是 “你能干什么的问题”。“你能干什么的问题”这句话里有两个关键的点:一个是 “你”,“你” 是用户;另一个是“干什么”,“干什么” 是 处理,在 WEB 系统中所有的处理都是由客户端对服务器发起的 HTTP资源请求。对于 http 资源请求一般有能力请求和没有能力请求,对应于 http 状态码则是200、403。在有 http 资源访问权限的基础上一般对于该请求所返回的 数据资源 也会有不同程度的限制,例如作者和管理员能看到和处理的文章数量不同。

把以上这HTTP资源和数据资源都抽象成系统资源,那么权限管理就简化成了系统中用户和系统资源的关系映射。

没图

用户分组

在公司里具有相同职责的人在系统中相应会有相同的权限,而不同的职责又跟公司的组织架构有关,所以很多时候设计权限管理的时候会吧公司组织结构也设计进来。事实上用相同能力和公司成员做映射的话完全可以把公司组织结构给扔掉,这种映射也就是 角色

利用以上分析现在可以做这么一个映射关系:用户-->角色-->系统资源。即通过给用户授予角色来实现用户->资源的映射过程。

在角色的处理上一般有单一角色和多角色两种方案,同时在这两种方案上对能单独对用户增减权限还要有额外的设计。

在公司里通常是一个萝卜一个坑,即一个人只有一种角色,但是实际过程中身兼多职的事情常常发生,所以一般我做权限设计底层都是多角色。

资源管理

如开头所说,把 “HTTP” 资源和系统中的 “数据资源” 都抽象成 “系统资源” 来处理,在系统中进行检查权限检查就可以变成 “是否拥有” 这么简单的事情了。到数据层面,用户拥有哪些权限也只是一维的数据,到这一步怎么存储就随意了。[我初次设计的时候是用关联表进行存储的,后来听从别人的建议并产考 sentry 权限控制组件,采用的 json 字符串方式进行存储。]

根据这些现在权限抽象处理和权限持久化差不多都解决了,但是这里会有第二个问题:如何管理这些权限?

对于一个小系统,十几二十几个功能的系统可能只需做个小表格把这些权限都列举一下,管理员根据角色去勾选一下就好,但是对于功能繁多的系统这样明显是行不通的,谁会去在几十上百个都差不多的名称里去找到你需要的那个权限名称去!

所以这些权限如何呈现给用户让他们自己去管理这又是一个问题!

呈现方式

这里先回顾一下后台菜单的排布方式,大概有一下几种

1. 功能简单,只要有一级导航就够了(图片来自element.eleme.io
2. 功能稍微复杂,有二级导航(图片来自element.eleme.io)
3. 更为复杂的系统,仅二级菜单无法满足系统功能(图片来自element.eleme.io)

1和2的形式都比较简单,基本没什么可说的。就直接说三级导航的这种后台怎么处理,然后一级二级这种的基本都不是问题。

处理三级导航系统的后台时我一般遵循这么一个原则:

  • 一级:以业务划分,相同业务线的在同一个一级导航下,同时在代码层面也进行命名空间的分割
  • 二级:以功能相关性划分,相似或有关联的功能化为一组。我通常二级导航不负责真正的功能只是对三级菜单的分组合并
  • 三级:这一级其实已经是具体的功能页了。具体到权限管理时会有菜单,页面,功能等区别
  • 处理三级导航的后台除了图三所示的那种所有导航都在左边,其实还可以一级导航在顶部,二三级在左边

在处理只到一级或者二级菜单的系统时,菜单是否展示往往依靠硬编码就解决了,因为这时候功能少,设计复杂了反而影响修改的灵活性,但是对于三级菜单的系统来讲要权限和菜单同步仅靠硬编码就很困难了,要做到足够的灵活可配置就需要设定一个规则进行约定了。

数据结构

先写一个三级菜单的菜单数据结构

[
    {
        "name":"一级导航1",
        "url": "/nav1",
        "chirdren":[
            {
                "name":"二级导航1", // 一般为菜单分组,只提供折叠功能而不提供导航到页面的功能
                "url": "/nav1/nav2-1",
                "chirdren": [
                    {   // 这一级才是真正的菜单
                        "name":"三级导航1",
                        "url":"/nav1/nav2-1/path1",
                    },
                    { 
                        "name":"三级导航2",
                        "url":"/nav1/nav2-1/path2",
                    },
                ]
            },
            {
                "name":"二级导航2",
                "url": "/nav1/nav2-2",
                "chirdren": [
                    {
                        "name":"三级导航1",
                        "url":"/nav1/nav2-2/path1",
                    },
                    { 
                        "name":"三级导航2",
                        "url":"/nav1/nav2-2/path2",
                    },
                ]
            }
        ]
    },{
        ...
    }
]

上面一级导航和二级导航都有一个 URL,实际上二级作为分组只有折叠的作用,一级导航如果在顶部,那个 URL 其实点击后跳转到的也是该分组下的一个三级导航。

另外这个数据结构只处理了一级导航>二级导航>三级导航的数据结构,三级导航是列表页的情况下时该三级导航可能还会关联 "新建页面[GET]","新建提交[操作|POST]","编辑页面[GET]","编辑提交[操作|PUT]","删除提交[操作|DELETE]", 这些项虽然不在菜单中展示,但是在权限管理页面这些却是必须的。

  • 从这一段开始我不再用一级导航、二级导航、三级导航这样的名称,而是用导航>分组>菜单这样功能比较明确的名称

上一节【呈现形式】有提到如何管理但是并没有真正解决三级菜单下的如何呈现的问题。现在提问:如果用和导航布局一样的层现方式来呈现权限管理是否可行呢?

这里还有另外一个问题,就是当我们打开菜单下的一个编辑页面不管我刷不刷新浏览器改页面上层的菜单和导航应该都是高亮的,这又该如何解决?

下面给一个我现在使用的权限管理数据结构

[
    {
        "name":"导航1",
        "index": "welcome",
        "groups": {
            "分组1": [
              "home.dashboard.index1",
              "home.dashboard.index2",
            ],
            "分组2": [
              "home.dashboard.index3",
            ],
        },
        "routes":{
            "welcome":{
                "name":"二级导航1", 
                "url": "/nav1/nav2-1",
            },
            "home.dashboard.index1": {
                "name":"菜单",
                "url": "/home/dashboard/index1",
                "type":"menu"
            },
            "home.dashboard.index1-1": {
                "name":"页面1-1",
                "url": "/home/dashboard/index1-1",
                "type": "page",
                "refer": "home.dashboard.index1",
            },
            "home.dashboard.index1-1": {
                "name":"页面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":"页面3-1",
                "url": "/home/dashboard/index2-1",
                "type": "page",
                "refer": "home.dashboard.index2",
            },
            "home.dashboard.index2-1": {
                "name":"页面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":"页面3-1",
                "url": "/home/dashboard/index3-1",
                "type": "page",
                "refer": "home.dashboard.index3",
            },
            "home.dashboard.index3-1": {
                "name":"页面3-2",
                "url": "/home/dashboard/index3-2",
                "type": "page",
                "refer": "home.dashboard.index3",
            },
        }
    },{
        ...
    }
]
  • 因为有 Request Method所以在 http 请求中一个 uri 并不能确定唯一一个请求,二者加一起才能唯一确定一个请求。而laravel框架刚好提供了路由别名的方案,即你可以给一个任意一个请求起一个别名,并且一个别名只能对应唯一一个 http request。我们可以根据这个别名生成 url,也能根据 Request 实例获得该实例的别名。关于别名的另一个好处就是只要我起名的方案不变 url 需要改变是不用担心的。所以在实际应用中我也是用别名设计的配置文件。

事实上在实际应用中我的配置文件路由节点长的是这个样子的

        'works'=>[
            'name'  =>'工作记录',
            'uri'   =>'/system/work',  'method'=>'get',
            'uses'  =>'HomeController@work',
            'limit-on'=>false,
            'log.file'   => '【{{user.name}}】访问了操作日志页',
            'throttle'=>100,
        ],

因为跟别名关联的除了路由,还有行为日志、访问限制、权限管理等行为,所以直接设计在一块好了,一个配置文件搞定一切,这样当配置项增加的时候也不用担心顾此失彼的事情了。

单页面应用下的权限控制

单页面应用前后端是完全分离的状态的,前端路由和后端API路由没有必然的联系。像前端 /#/post/1234这样的路由调用后端接口则可能为post?id=1234这样的形式,二者的关联只在业务逻辑代码的 ajax 请求那块,一般情况下是靠前后端程序员约定。而且前端路由相对会比后端少,因为前端的一个功能复杂的页面会对应后端许多接口。所以前后端的权限怎么控制?

原则上可以制定一套规则同时适应于前后端路由和权限控制,但由于要考虑前后端权限控制和路由的问题以及一些未定义的场景这个方案将会很复杂。而且如果是团队协同开发,那么前后端成员都必须在了解这个方案和命名体系的前提下才能愉快的协作。事实上多人协作开发的过程中最大的问题就是知识同步

另一个问题,在上一大节中我们可以了解到权限控制可以简化成一个类似查 hash 表的情景,那么这个 hash 表谁来维护,前端?后端?还是前后端同时维护一份。在这里面任意一种方案都有一个不可逃避的问题就是配置同步,1.如果前端维护那么这个hash 表要同步给后端,2.如果后端维护要同步给前端,3. 如果同时维护,那就要保持同步修改。 一般来讲(1.)的实现会更困难,(2.)实现的前提是前端必须在后端服务的基础上进行开发,而且很大层面上是在前后端用一个方案这个前提下。方案(3.)相对来讲权限表的维护成本更高一点,但其实这个维护也只是在设计之初前后端双方同时维护而已,产品成型之后的修改远没刚开始的修改更频繁,而且方案(3.)可以解开上一段所描述的知识同步的问题,因为只要约定某个 key 是控制某个功能,双方根据这个约定去开发各自的功能基本不会有什么大问题,同时各自路由的命名方案也可以由此解开。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,515评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 当我还是认为自己是个孩子的时候,你来到了这个世界,来到了我身边,我有些措手不及。但也得面对你-儿子。 渐渐的,你学...
    健康伦阅读 288评论 0 0
  • 1 “嗨,小姐,请问您想吃什么?” “Ah,我,不知道,可以再等我一会儿吗?” -三个小时后- “抱歉,小姐,您还...
    森森_line阅读 395评论 3 3