系统权限树——JSTree学习

作为一个后端开发,在开发管理后台,一定会遇到【角色-资源管理】,那么,如何使用开源框架,快速的设计一棵资源权限树呢?

  1. jsTree API文档(中文)
  2. js Tree API官方文档

1. 数据库的设计

1. 系统角色表[sys_role]

CREATE TABLE `sys_role` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `ROLE_CODE` varchar(50) DEFAULT NULL COMMENT '角色编码',
  `ROLE_NAME` varchar(128) DEFAULT NULL COMMENT '角色名称',
  `STATUS` varchar(1) DEFAULT NULL COMMENT '是否启用,1 启用,0 禁用',
  `ROLE_DESC` varchar(500) DEFAULT NULL COMMENT '角色描述',
  `CREATOR` varchar(40) DEFAULT NULL COMMENT '创建者',
  `CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `UPDATER` varchar(40) DEFAULT NULL COMMENT '修改者',
  `MODIFY_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `i_role_code` (`ROLE_CODE`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统角色表';

2. 系统资源表[sys_resource]

CREATE TABLE `sys_resource` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `RES_CODE` varchar(50) DEFAULT NULL COMMENT '资源编码',
  `RES_NAME` varchar(128) DEFAULT NULL COMMENT '资源名称',
  `RES_KEY` varchar(40) DEFAULT NULL COMMENT '资源关键字',
  `RES_TYPE` char(1) DEFAULT NULL COMMENT '资源类型,1 模块,2  菜单,3 操作',
  `PARENT_RES_CODE` varchar(50) DEFAULT NULL COMMENT '父级资源编码',
  `DISPLAY_ORDER` int(11) DEFAULT NULL COMMENT '同级中显示顺序号',
  `IS_ENABLE` char(1) DEFAULT NULL COMMENT '是否启用,1 启用,0 禁用',
  `DEL_FLAG` char(1) DEFAULT NULL COMMENT '删除标识,0  未删除,1 已删除',
  `CREATOR` varchar(40) DEFAULT NULL COMMENT '创建者',
  `CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `UPDATER` varchar(40) DEFAULT NULL COMMENT '修改者',
  `MODIFY_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 
  COMMENT '修改时间',
  `REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `i_resource_code` (`RES_CODE`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

3. 系统角色-资源表[sys_role_res_ref]

CREATE TABLE `sys_role_res_ref` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `ROLE_ID` int(11) DEFAULT NULL COMMENT '角色ID',
  `RES_CODE` varchar(50) DEFAULT NULL COMMENT '资源ID',
  PRIMARY KEY (`ID`),
  KEY `i_role_res_ref` (`ROLE_ID`,`RES_CODE`)
) ENGINE=InnoDB AUTO_INCREMENT=8339 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统角色资源关联表';

需要注意的是,角色和资源是n:n的关系,故需要一个中间表让两表进行关联。

2. 后端获取角色资源

1. 确定响应对象的key

系统权限树——JSTree学习_第1张图片
jstree.js源码

由上面jstree.js源码可知。我们想要借助jsTree形成资源树,需要使用jstree.js源码中已定义的值(也就是key)。例如:children、text、state、id、selected。

2. 多叉资源树的遍历

资源树是一个多叉树,当我们得到它的根节点(此处使用00-00-00-00)那么我们可以遍历整棵树。

在【sys_resource 资源表】中含有【PARENT_RES_CODE 父级资源编码】字段。即子节点中包含父节点的信息。

对于树的遍历,一般采用递归算法。

对于递归方法,我们可以将其看做一个普通方法,这个方法的返回值就是【方法开始-递归方法之间的逻辑】。

对于整棵树来说,我们采用【前序遍历】,第一次访问父节点时,处理父节点。

递归要含有【递归出口】,即当遇到【子节点】时,采取某些操作,终止递归。

需要注意的是,此处的【角色】有两种,一类是【登录用户的角色】,一类是【登录用户创建的角色】,他们是父子集关系。

3. 代码分析

此处的Mapper层逻辑,使用的是mybaties-generate自动生成。

@Autowired
    private SysResourceMapper sysResourceMapper;

    @Autowired
    private SysRoleResRefMapper sysRoleResRefMapper;

    public JSONObject getSysRescTreeJson(int roleId) {
        SysResourceExample example = new SysResourceExample();
        example.createCriteria().andResCodeEqualTo("00-00-00-00");
        //获取根节点的节点对象
        List rootNodes = sysResourceMapper.selectByExample(example);
        SysResource rootNode = null;
        if (rootNodes != null && rootNodes.size() > 0) {
            rootNode = rootNodes.get(0);
        }
        //获取登录用户创建的角色资源(子集)
        Set roleRescSet = getRoleRescSet(roleId);
        //获取资源列表对象
        Map treeInfoMap = getNodeInfoMap(rootNode, roleRescSet);
        return (JSONObject) JSONObject.toJSON(treeInfoMap);
    }

1. 获取登录用户创建的角色资源

  private Set getRoleRescSet(Integer roleId) {
        //获取角色资源列表
        Set roleRescSet = new HashSet();
        SysRoleResRefExample example = new SysRoleResRefExample();
        example.createCriteria().andRoleIdEqualTo(roleId);
        List sysRoleResRefs = sysRoleResRefMapper.selectByExample(example);
        //放入Set集合,以便后续快递比较
        if (sysRoleResRefs != null && sysRoleResRefs.size() > 0) {
            for (SysRoleResRef sysRoleResRef : sysRoleResRefs) {
                roleRescSet.add(sysRoleResRef.getResCode());
            }
        }
        return roleRescSet;
    }

2. 多叉资源树的变量-获取资源信息

可以看到,我们在后台代码中使用了id、text、state、children、selected值作为key,以便和jsTree对应。

   //参数值一个是当前节点对象、用户创建的角色资源列表
   private Map getNodeInfoMap(SysResource node, Set roleRescSet) {
        //最终输出的是第一次创建的map
        Map map = new HashMap();
        map.put("id", node.getResCode());  //角色编码
        //角色名称[角色关键字]
        map.put("text", node.getResName() + "[" + node.getResKey() + "]");  
        Map statMap = new HashMap(); 
        //资源状态(引用传递,此处保留的是空对象)
        map.put("state", statMap);  

        //获取当前节点的子节点列表信息
        List resInfoByParentResCode = findResInfoByParentResCode(node.getResCode());
        //获取shiro中的资源信息(登录用户的角色信息。UserAuthorizingInfo是自定义对象,里面含义一个Set字段,包含了登录用户资源信息)
       Set currentResourcesSet= (UserAuthorizingInfo)(SecurityUtils.getSubject().getPrincipal()).getUserResources();
       //递归出口 (非子节点进行处理)
       if (resInfoByParentResCode != null && resInfoByParentResCode.size() > 0) {
            //处理子节点(子节点信息)
            List> children = new ArrayList>();
            //遍历该节点下的子节点
            for (SysResource childrenNode : resInfoByParentResCode) {
                //若是当前登录用户的角色
                if (currentResourcesSet.contains(childrenNode.getResCode())) {
                    //获取子节点的信息,保存到List中
                    Map nodeInfoMap = getNodeInfoMap(childrenNode, roleRescSet);
                    children.add(nodeInfoMap);
                }
            }
            //处理完毕,将子节点保存到children中
            map.put("children", children);
      
            map.put("selected", false);
        } else {
            //若是子节点,并且是登录用户创建的角色资源。那么输出被选中。
            if (roleRescSet.contains(node.getResCode())) {
                statMap.put("selected", true);
            } else {
                statMap.put("selected", false);
            }
        }
        return map;
    }

3. 获取该节点的子节点

  private List findResInfoByParentResCode(String parentResCode) {

        SysResourceExample sysResourceExample = new SysResourceExample();
        //查询该资源的子级资源,状态:未删除、启动
        sysResourceExample.createCriteria().andParentResCodeEqualTo(parentResCode).andDelFlagEqualTo("0").andIsEnableEqualTo("1");
        List sysResources = sysResourceMapper.selectByExample(sysResourceExample);
        return sysResources;

    }

4. Controller层代码

@Controller
@RequestMapping("role")
public class RoleAction {
    private final Logger logger = LoggerFactory.getLogger(RoleAction.class);

    @Autowired
    private RoleService roleService;


    /**
     * 获取资源节点树数据
     */
    @RequestMapping(value = "/getRescTreeData")
    @ResponseBody
    public ResponseVo getRescTreeData(Integer roleId) {
        ResponseVo respVo = new ResponseVo();
        try {
            JSONObject treeData = roleService.getSysRescTreeJson(roleId);
            System.out.println(JSONObject.toJSONString(treeData));
            respVo.setData(treeData);  //private Object data;
            respVo.setRetcode(ResponseVo.SUCC);
            respVo.setMessage("获取资源树数据成功");
        } catch (Exception e) {
            respVo.setRetcode(ResponseVo.FAIL);
            respVo.setMessage("获取资源树数据失败 原因:" + e.getMessage());
            logger.debug("获取资源树数据失败 原因:", e);
            logger.error("获取资源树数据失败 原因:" + e);
        }
        return respVo;
    }
}

3. 前端生成资源树

1. 展示权限树

        
<%--必须含有portlet-title下面caption和tools才能生效--%>
角色管理

此处就需要一个id为tree的div来生成树,是不是很方便~~~

//设置权限树
function setRescTree(roleId) {

    $.ajax({
        url: "./role/getRescTreeData?roleId=" + roleId,
        type: "POST",
        contentType: "application/json;charset=utf-8",
        dataType: "json",
        success: function (json) {
            if (json.retcode == 200) {
                var treeData = json.data;
                //返回一个存在的引用
                var tree = jQuery.jstree.reference("#tree");
                if (tree != null) {
                    tree.destroy();
                }
                // 当根节点(root)第一次加载时触发。
                $("#tree").bind("loaded.jstree", function (e, data) {
                    //禁止编辑节点(children_d是所有子级均不可编辑;若是使用children,只是直接子级不能编辑)
                    //#代表是整棵树都不能被编辑
                    data.instance.disable_node($('#tree').jstree(true)._model.data['#']['children_d']);
                }).jstree({
                    'plugins': ["wholerow", "checkbox", "types"],
                    'checkbox': {cascade: "", three_state: true},
                    'core': {
                        "themes": {
                            "responsive": false
                        },
                        "expand_selected_onload": true,
                        'data': treeData
                    },
                    "types": {
                        "default": {
                            "icon": "fa fa-folder icon-state-warning icon-lg"
                        },
                        "file": {
                            "icon": "fa fa-file icon-state-warning icon-lg"
                        }
                    }
                });

                // 当所有节点都加载完毕时触发。
                $("#tree").on("ready.jstree", function (event, data) {
                    //禁止展开根节点(00-00-00-00)下的第一层-孩子节点
                    data.instance.close_node($('#tree').jstree(true)._model.data['00-00-00-00']['children'], false);
                });
            } else {
                layer.alert(json.message, {icon: 2});
            }
        },
        error: function () {
            layer.alert("获取资源树数据失败", {icon: 2});
        }
    });
}

2. 保存权限树

如何编辑权限树,使其保存到数据库里面呢?

前端上送资源编码

使用$("#tree").jstree().get_checked();获取整棵树被选中的资源。

 //绑定点击事件
    var save = function () {
        var resc = $("#tree").jstree().get_checked();
        //点击上交时,将资源树id提交上去。
        var formData= $('.edit-from').serializeJsonStr();
        $.ajax({
            url: "./role/save",
            data: JSON.stringify({
                rescList:resc
            }),
            type: "POST",
            contentType:  "application/json;charset=utf-8",
            dataType: "json",
            success:function (json) {

            }
        })

    }

后台获取资源编码

    @ResponseBody
    @RequestMapping("save")
    public ResponseVo saveUserInfo(@RequestBody SysRoleAndRescVo sysRoleAndRescVo){
        logger.info(JSON.toJSONString(sysRoleAndRescVo));
        //用户操作
        return null;
    }

请求参数:
本质上是上送一个id的List集合,故我们直接使用List获取前端上送的资源。

public class SysRoleAndRescVo {
    
    private List rescList;

    public List getRescList() {
        return rescList;
    }

    public void setRescList(List rescList) {
        this.rescList = rescList;
    }
}

你可能感兴趣的:(系统权限树——JSTree学习)