一次性搞定权限树遍历(通用解决方案)


prtyaa
prtyaa 2023-12-30 22:44:36 66165
分类专栏: 资讯

一、权限树是什么

在写项目的权限管理模块、用户系统的时候经常碰见类似的树结构我们一般习惯称之为权限树,权限树应用的地方有很多,比较常见的有:权限管理时候的树状图(如上图),页面左侧的一二三级的菜单,物品分类的树状菜单。

在实际项目中这种权限结构,数据库设计一般是这样的:

其中我们需要通过id和父id来遍历树状图,这种结构非常简单也很好理解,但对于新手编码来说就不是特别的友好了,特别是培训出来的朋友可能对数据结构和算法不是特别了解,经常性会参考网上一些不是很优秀的写法,通过递归的方式反复的查询数据库来逐层往上级遍历,这种写法的运行起来比较慢,网络开销比较大,如下图:

二、如何遍历权限树

下面我们一起思考如何写一个权限树的遍历并且让其适用与大部分情况

1、设计结构

根据上文数据库,可以设计出一个简单有效的结构:

  • data:数据库表的任意一行权限
  • childrenNode:data节点下所有的权限集合

然后添加上get、set方法,为了方便再增加两个构造函数,用于对元素初始化(空构造函数也不忘记),因为我们无法得知data具体是什么类型,所有使用泛型让data可以成为任何类,增加通用性。

public class TreeNode<T> {
    //数据内容
    private T data;
    //这个节点下的子节点
    private List<TreeNode<T>> childrenNode = new ArrayList<>();
 
    public T getData() {
        return data;
    }
 
    public void setData(T data) {
        this.data = data;
    }
 
    public List<TreeNode<T>> getChildrenNode() {
        return childrenNode;
    }
 
    public void setChildrenNode(List<TreeNode<T>> childrenNode) {
        this.childrenNode = childrenNode;
    }
 
    public TreeNode(T data, List<TreeNode<T>> childrenNode) {
        this.data = data;
        this.childrenNode = childrenNode;
    }
 
    public TreeNode() {
    }
}

2、获取权限数据

之前说到反复的去数据库获取数据会让网络开销变大,所以我们应该一次性将所有需要的权限数据全部取出(有的时候是取出全部的数据,有时取得某个角色的权限数据),将取到数据放入一个list中。

一个节点类应该还具有获取自己子节点的能力,在TreeNode类中添加获取子节点方法:

通过data的id和每条数据的父id对比相同的放入子节点的集合中,并且从数据集合中删除(这样做只要权限数据集合变成空了就可以算是树遍历完成)

public List<TreeNode<T>> childrenNode(List<T> datas, String idName, String fidName) {
        ConvertTree<T> convertTree = new ConvertTree<>();
        String idValue = convertTree.getFieldValue(data, idName);
        List<T> collect = datas.stream()
                .filter(date -> idValue.equals(convertTree.getFieldValue(date, fidName)))
                .collect(Collectors.toList());
        datas.removeAll(collect);
        for (T node : collect) {
            TreeNode<T> treeNode = new TreeNode<>();
            treeNode.setData(node);
            childrenNode.add(treeNode);
        }
        return childrenNode;
    }

由于无法知道具体的类也就不知道id字段和父id字段,采用反射的方式获取字段的值

public String getFieldValue(T o, String fieldName) {
        //得到class
        Class cls = o.getClass();
        //得到所有属性
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            try {
                //打开私有访问
                field.setAccessible(true);
                //获取属性
                if (field.getName().equals(fieldName)) {
                    Object result = field.get(o);
                    if (result == null) {
                        return null;
                    }
                    return result.toString();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        throw new RuntimeException("获取属性值失败");
    }

3、从数据中找出根节点

我们首先需要定位一棵树最顶端的节点,也就是根节点“祖宗节点”。

新建一个类,并添加获取根节点方法。这个方法需要权限数据的集合,id的字段名,父id的字段名

思路:判断权限集合是否为空,取出集合中第一个元素,用过递归一层一层往上找,一直到找不到父节点为止,将这个得到的根节放到我们的树结构中,并且通过childrenNode来获取子节点他的子节点。

private TreeNode<T> getRootNode(List<T> datas, String idName, String fidName) {
        if (datas.isEmpty()) {
            return null;
        }
        T node = datas.get(0);
        T rootNode = getRootNode(datas, idName, fidName, node);
        TreeNode<T> rootTreeNode = new TreeNode<>();
        datas.remove(rootNode);
        rootTreeNode.setData(rootNode);
        rootTreeNode.childrenNode(datas, idName, fidName);
        return rootTreeNode;
    }

这个方法会不停的递归直到早不到父节点为止

private T getRootNode(List<T> datas, String idName, String fidName, T node) {
        T fNode = null;
        String fieldValue = getFieldValue(node, fidName);
        for (T data : datas) {
            if (getFieldValue(data, idName).equals(fieldValue)) {
                fNode = data;
                break;
            }
        }
        if (fNode == null) {
            return node;
        } else {
            return getRootNode(datas, idName, fidName, fNode);
        }
    }

4、根据根节点形成一棵树

先获取根节点,然后使用forChildren方法遍历根节点的子集,一级一级的往下查找子级,最后返回树。

private TreeNode<T> getTree(List<T> datas, String idName, String fidName) {
        //获取树根
        TreeNode<T> rootNode = getRootNode(datas, idName, fidName);
        //遍历树根的子集
        List<TreeNode<T>> childrenNode = rootNode.getChildrenNode();
        forChildren(datas, idName, fidName, childrenNode);
        //此时树已经形成
        return rootNode;
    }

遍历子集,将子集的子集记录下来,存入needForList,如果needForList不为空就一直递归,needForList为空的时候说明这棵树的所有相关的节点都找到了并且都通过childrenNode方法处理成TreeNode类。

private void forChildren(List<T> datas, String idName, String fidName, List<TreeNode<T>> childrenNode) {
        //需要遍历的集合
        List<TreeNode<T>> needForList = new ArrayList<>();
        for (TreeNode<T> tTreeNode : childrenNode) {
            List<TreeNode<T>> treeNodes = tTreeNode.childrenNode(datas, idName, fidName);
            needForList.addAll(treeNodes);
        }
        if (!needForList.isEmpty()) {
            forChildren(datas, idName, fidName, needForList);
        }
    }

三、形成森林

很多时候权限树并没有一个统一的根节点,如下图:

系统管理、软件管理、统计分析各是一棵树,这时候我们就需要把每一棵树都遍历出来,添加形成森林的方法:

只需要循环执行形成树的方法,一直到所有的权限数据的集合为空的时候结束,将所有的遍历出来的树放入集合中,返回集合。

public List<TreeNode<T>> getForest(List<T> datas, String idName, String fidName) {
        List<TreeNode<T>> forest = new ArrayList<>();
        while (!datas.isEmpty()) {
            TreeNode<T> tree = getTree(datas, idName, fidName);
            forest.add(tree);
        }
        return forest;
    }

四、使用

1.仅支持java8,id不可重复

2.调用方法就可以使用了

public List<TreeNode<AuthResource>> toTree() {
        //获取数据集合
        List<AuthResource> list = authResourceRepository.findAll();
        //创建工具类
        ConvertTree<AuthResource> convertTree = new ConvertTree<>();
        //生成森林
        List<TreeNode<AuthResource>> forest = convertTree.getForest(list, "id", "parentId");
        return convertTree.getForest(list);
    }

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=32871
赞同 0
评论 0 条
prtyaaL1
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1486
银河麒麟打印带有图像的文档时出错  1404
银河麒麟添加打印机时,出现“server-error-internal-error”  1193
统信操作系统各版本介绍  1115
统信桌面专业版【如何查询系统安装时间】  1113
统信桌面专业版【全盘安装UOS系统】介绍  1067
麒麟系统也能完整体验微信啦!  1026
统信【启动盘制作工具】使用介绍  671
统信桌面专业版【一个U盘做多个系统启动盘】的方法  615
信刻全自动档案蓝光光盘检测一体机  526
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!