如果只想直接看算法的,可以跳到第3个标题;其他几个标题的内容作为背景铺垫,介绍为什么要写这个算法,以及可以解决什么实际问题,解决之后的效果如何。因为如果不联系实际,一些人会认为树之类的数据结构没什么用武之地,太抽象。结合其他几个标题的内容可以帮助理解。
1 问题背景分析
最近在公司做一个知识库分享平台的用户权限的后台功能,大概是这样的:
权限分为很多级,如下图所示:
当然所有权限不止这些,分类下面还有软件,软件下面还有版本……资源树太大,我就不全画出来了。
举个例子来说明上图。假设一个用户A,他有主题2的所有权限,那么他肯定也拥有主题2下面的分类3和分类4的所有权限,即拥有了一棵子资源树;同时假设A用户还拥有academy这棵子资源树的所有权限。也就是说,一个用户的资源树,很可能有很多棵。对于全局管理员B,他当然只有一棵完整的资源树。
下面说一下赋权列表,一个用户(master)给另一个用户(slave)赋权的时候,首先得到master的所有权限的资源树(可能一棵,也可能很多棵),然后得到slave用户的所有资源树。在master用户的赋权界面选择slave用户的id之后,从用户体验的角度来讲,我们希望只显示master可以赋给slave的权限,即master拥有,而slave没有的资源,master用户通过给这些权限打勾来赋权。也就是要把二者有交集的资源树做减法。
那么在后台需要实现的功能就是:根据用户id得到二者的所有资源树,下一步,得到二者资源树的交集(可能为空),判断master的某个资源树是否是某个交集树的父级,如果是则删除,返回删除后的不完全树;如果不是,则直接返回master的该资源树。
语言描述还是太繁琐,用下面的图进行表示:无论master和slave的资源树有没有交集,三种情况下,最终返回的树都是蓝色部分。
2 从数据库得到资源树
Resource.java
public class Resource implements java.io.Serializable{
private static final long serialVersionUID = -4790317033687903765L;
private Integer resourceId;
private Integer parentId;
private String resourceType;
private String resourceUrl;
private String resourceName;
private String remark;
private Boolean isDeleted;
private Date createdTime;
private Date modifiedTime;
//省略get和set方法
}
显然这个resource是没法直接拿来生成树的,需要做一个类
ResourceExt.java
/**
* 简化的资源树
*
* @author wujiang
* @version 1.0.0.
* @date 2016/9/19
*/
public class ResourceExt implements Serializable{
private Resource resource;
//关于该资源名称的描述,我在另一个类中写了生成方法,这里省略
private String description;
private List<ResourceExt> childrenList;
//省略get和set方法
现在需要使用迭代将数据库中查出的Resource组成的List转化为资源树
注意,这里的
/**
* 将List<Resource>转换为List<ResourceExt>
*/
public List<ResourceExt> ResourcetoExt(List<Resource> resourceList){
List<ResourceExt> resourceExtList = new ArrayList<ResourceExt>();
List<Resource> childList = new ArrayList<Resource>();
for(Resource rs:resourceList){
ResourceExt re = new ResourceExt();
re.setResource(rs);
Integer it = rs.getResourceId();
re.setDescription(descriptionWriter.getDescription(it));
childList = resourceManager.getSubResource(rs.getResourceUrl());
if(childList!=null){
re.setChildrenList(this.ResourcetoExt(childList));
}else re.setChildrenList(null);
resourceExtList.add(re);
}
return resourceExtList;
}
注意,这里的List< Resource >并不是某个用户所有权限组成的List,而是他所有资源树的顶点资源组成的List。
取出这个List的方法很简单,就是从数据库中查出他的所有资源,将每个资源的Id放入一个List< Integer >中,遍历,如果哪个Resource的parentid不在这个List中,就把该Resource设置为顶点Resource。
至于如何取到master和slave的公共权限以及master拥有而slave没有的权限呢?将两个用户的资源id得到的List(因为资源id不会重复,所以可以看做是set),求交集即可。代码如下:
/**
* 得到master和slave两个用户的公共权限
*/
public List<Resource> getCheckedList(List<Integer> masterIdList,List<Integer> slaveIdList){
List<Integer> tempList = new ArrayList<Integer>();
tempList.addAll(masterIdList);
tempList.retainAll(slaveIdList);
return this.IdtoResource(tempList);
}
/**
* 得到master拥有而slave没有的权限,即master可以赋给slave的权限
*/
public List<Resource> getUncheckedList(List<Integer> masterIdList,List<Integer> slaveIdList){
List<Integer> tempList1 = new ArrayList<Integer>();
tempList1.addAll(masterIdList);
tempList1.removeAll(slaveIdList);
return this.IdtoResource(tempList1);
}
/**
* 根据idList得到List<Resource>
*/
public List<Resource> IdtoResource(List<Integer> list){
List<Resource> resourceList = new ArrayList<Resource>();
for(Integer i:list){
resourceList.add(resourceManager.selectByPrimaryKey(i));
}
return resourceList;
}
将这两个得到的List取出头结点,然后按照ResourceToExt方法就可以得到需要的若干棵父类资源树List< ResourceExt > masterExtHeads, 和子类资源树List< ResourceExt > slaveExtHeads。 这两个资源树集合是我们在第三部分中要进行处理资源树的集合。
3 判断某个子树是否属于某个父树,如果属于,从父树中删除子树
这里的大概思路是:想要从父资源树中删除子资源树,首先判断子资源树的顶点资源是否是父资源树顶点资源的后代,用一个IteratorHelper的类来记录,如果是,则返回true,并且将该节点的父类资源记录下来,然后进行删除操作。否则,返回false和null。
/**
* @author wujiang
* @version 1.0.0.
* @date 2016/9/27
*/
public class IteratorHelper {
private boolean flag;
private ResourceExt parentResourceExt;
public IteratorHelper(boolean flag, ResourceExt parentResourceExt) {
this.flag = flag;
this.parentResourceExt = parentResourceExt;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public ResourceExt getParentResourceExt() {
return parentResourceExt;
}
public void setParentResourceExt(ResourceExt parentResourceExt) {
this.parentResourceExt = parentResourceExt;
}
}
删除的方法是,首先找到想要删除的资源的父节点,遍历其所有子资源节点,如果id与想要删除的相同,就删除。为什么想要删除子树,删除其头结点就可以?因为在JVM中,一旦该子树的头结点被删除,之后的所有后代都会因为没有被引用而被当做垃圾回收掉。所以不需要再迭代删除子树中的每一个子节点。
/**
* ListProcessor处理器
*
* @author wujiang
* @version 1.0.0.
* @date 2016/9/19
*/
@Service
public class ListProcessor {
/**
* 通过传入的两个list得到AuthorizeMap
*
* @param masterList
* @param slaveList
* @return AuthorizeMap
*/
//传入的参数就是第二部分中得到的若干父资源树和子资源树的集合
public List<ResourceExt> getAuthorizeMap(List<ResourceExt> masterExtHeads, List<ResourceExt> slaveExtHeads) {
for (ResourceExt masterExt : masterExtHeads) {
for (ResourceExt slaveExt : slaveExtHeads) {
if (this.traverseTree(masterExt, slaveExt.getResource().getParentId()).isFlag()) {
this.deleteNode(masterExt, slaveExt);
}
}
}
return masterExtHeads;
}
/**
* 从父资源树的顶点中序遍历一棵树
* 如果找到某个想要删除的资源的父资源,返回true并得到该父类资源;否则返回false,父类资源为空
* 目的是根据parentid找到对应的ResourceExt
*/
public IteratorHelper traverseTree(ResourceExt headResource, Integer parentId) {
IteratorHelper iteratorHelper = new IteratorHelper(false, null);
if (headResource.getResource().getResourceId().equals(parentId)) {
iteratorHelper.setFlag(true);
iteratorHelper.setParentResourceExt(headResource);
} else {
List<ResourceExt> childrenList = headResource.getChildrenList();
if (childrenList != null && childrenList.size() > 0) {
for (ResourceExt rs : childrenList) {
if (iteratorHelper.isFlag()) {
return iteratorHelper;
}
iteratorHelper = this.traverseTree(rs, parentId);
}
}
}
return iteratorHelper;
}
/**
* 删除节点及其下的所有子节点
*
* @param
* @return
*/
public void deleteNode(ResourceExt headExt, ResourceExt slaveResource) {
//得到slaveResource的父ResourceExt
ResourceExt parentExt = this.traverseTree(headExt, slaveResource.getResource().getParentId()).getParentResourceExt();
Integer toDeleteId = slaveResource.getResource().getResourceId();
if (parentExt != null) {
this.deleteChildNode(parentExt, toDeleteId);
}
}
/**
* 相当于通过该资源的上一级父类资源删除其本身
*
* @param
* @return
*/
public void deleteChildNode(ResourceExt parentExt, Integer toDeleteId) {
List<ResourceExt> childList = parentExt.getChildrenList();
for (ResourceExt re : childList) {
if (re.getResource().getResourceId().equals(toDeleteId)) {
childList.remove(re);
break;
} else
continue;
}
}
}
4 前端效果展示
其实前面我省略了很多步骤,只是把最关键的几步放上来了,诸如数据库因为高度不冗余,无法直接查出某个用户全部资源之类的坑我都遇到了。。。。总之就是遇到问题解决问题吧。直接按照上面的代码跑不一定能跑通,因为我删除了一些代码只留下最关键的,给需要的人提供一些解决问题的思路。代码稍作修改应该就可以使用。
我把最终生成的树返回给前端,在页面上就会有如下的显示效果:
当一个用户输入另一个用户的userId想要给他赋权时,前端就会这样显示:
页面中,你并不知道被赋权的人有哪些权限,你只知道你可以赋给他哪些权限,并且是按照权限的层级树状展开的
说明
本人水平有限,不当之处希望各位高手指正。另外,文中的插图都是我自己在word的smart art中绘制的,看起来不精致请见谅。
如有转载务必注明出处
http://blog.csdn.net/antony9118/article/details/52704457
谢谢。