在实际项目中这种权限构造,数据库设计一样平常是这样的:
个中我们须要通过id和父id来遍历树状图,这种构造非常大略也很好理解,但对付新手编码来说就不是特殊的友好了,特殊是培训出来的朋友可能对数据构造和算法不是特殊理解,常常性会参考网上一些不是很精良的写法,通过递归的办法反复的查询数据库来逐层往上级遍历,这种写法的运行起来比较慢,网络开销比较大,如下图:
二、如何遍历权限树
下面我们一起思考如何写一个权限树的遍历并且让实在用与大部分情形

根据上文数据库,可以设计出一个大略有效的构造:
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); }
3.代码:
https://github.com/guoyixing/converttree
原文链接:https://mp.weixin.qq.com/s/k0ih4IT9xaIKSrqw9QLs4Q