天天看点

十五、WEB项目开发之权限分配系统

(一)案例背景介绍

  这里的“权限管理系统”不是依托于Shiro框架,而是依托于“ZTree”插件,纯手工打造。

  在做这部分功能时,主要在整个权限系统的设计方面走了一些弯路,最后总结出来的经验就是:先将整个ZTree展示出来,然后再分别实现整个功能。最终效果图如下:

十五、WEB项目开发之权限分配系统

  如上图所示,整个权限管理界面分为三颗树:用户树,用户组树和菜单树。基本功能就是:点击用户,出现关联的用户组,可以为该用户分配用户组;点击用户组,出现关联的菜单,可以为该用户组分配菜单。

(二)重难点

  用户树和用户组树展示起来很简单,查询所有数据,结合ZTree的简单JSON数据格式,即可构造成树。而菜单树就没有看起来的那么简单了,这里我主要以菜单树为例,讲解设计的基本思路,以及注意事项。

(三)具体实现

  1.设计表

  这里主要有两张表:菜单表(sys_menu)和Action表(sys_action)。

  这里涉及到一个问题:为什么设计成两张表,而不是一张菜单表(sys_menu)搞定所有?

  关于这个问题,其实有很多原因,这里我就讲下主要原因:我们在为一个用户组分配可操作的Action节点时,我们可能只给该用户分配某个菜单项下的部分Action,从而实现相对细粒度的控制。所以我们在构建关联关系时,既要建立用户组和Menu项的关联关系(sys_group_menu表),又要建立用户组和Action项的关联关系(sys_group_action)。从前面的分析可知,Menu(菜单项)更多的是作为一系列Action的容器,如下图:

十五、WEB项目开发之权限分配系统

比如“内容管理”菜单项下就包括“广告管理。。。”等一系列 的Action。这光靠一张表是难以实现上述所有功能的。

  (1)菜单表(sys_menu)

CREATE TABLE `sys_menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(50) DEFAULT NULL COMMENT '组名',
  `url` varchar(200) DEFAULT NULL COMMENT '访问地址',
  `parent_id` int(11) DEFAULT '0' COMMENT '父菜单ID',
  `order_num` int(2) DEFAULT NULL COMMENT '排序数字',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;      

  (2)Action表(sys_action)

CREATE TABLE `sys_action` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `menu_id` int(11) DEFAULT NULL COMMENT '菜单id',
  `name` varchar(50) DEFAULT NULL COMMENT '操作名',
  `url` varchar(200) DEFAULT NULL COMMENT '访问地址',
  `method` varchar(50) DEFAULT NULL COMMENT '操作',
  PRIMARY KEY (`id`),
  KEY `fk_menu_id` (`menu_id`),
  CONSTRAINT `fk_menu_id` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;      

  2.构建树

  这里又涉及到另外一个问题:怎么样在一棵树里展示两种节点,同时还要建立父子关系?

  关于这个问题,我们需要从ZTree的特性出发。在利用简单JSON数据格式建立树时,我们可以设置“显示节点”以及“父节点”ID的样式:

十五、WEB项目开发之权限分配系统

这里我们无法再设置“​​

​idKey : "id"​

​​”,因为菜单表(sys_menu)和Action表(sys_action)都有id,两张表查出的id很容易出现重复,导致ZTree树的展示混乱。所以这里我们设置为“​

​idKey : "comboId"​

​​”和“​

​pIdKey : "comboParentId"​

​​”,combo即混合,顾名思义,将两张表的id混合起来。

  具体实现就是:所有的菜单表(sys_menu)查询出来的id和parentId全部进行处理,加上“MENU_”前缀;所有的Action表(sys_action)查询出来的id全部进行处理,加上“ACTION_”前缀,查询出来的menuId(其实也就是父节点的意思,不过菜单节点的父节点依然是菜单节点,而Action的父节点是菜单节点,所以不叫parentId,而叫menuId)全部进行处理,加上“MENU_”前缀。处理过后出现的JSON数据如下:

data": [

    {
        "id": 1,
        "name": "内容管理",
        "comboId": "MENU_1",
        "comboParentId": "MENU_0",
        "open": true,
        "isParent": "true"
    },
    {
        "id": 1,
        "name": "广告管理",
        "comboId": "ACTION_1",
        "comboParentId": "MENU_1",
        "open": false
    },
    {
        "id": 2,
        "name": "商户管理",
        "comboId": "ACTION_2",
        "comboParentId": "MENU_1",
        "open": false
    }

]      
data.data.push({
                    id : 0,
                    //这是菜单项,加上菜单前缀
                    comboId : common.menuPrefix.PREFIX_MENU + "0",
                    name : "菜单组",
                    open : true,
                    isParent : true,
                    nocheck:true
                });