likes
comments
collection
share

Java 通用树状菜单返回工具类 TreeMenuUtil

作者站长头像
站长
· 阅读数 52

1. 实体类

子菜单用children存放

import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author eleven
 * @date 2022/4/12 16:39
 * @apiNote 系统字典表
 */
@Data
public class SysDict {
    @TableId(type = IdType.AUTO)
    @ExcelProperty("id")
    private Integer id;
    @ExcelProperty("父级id")
    private Integer parentId;
    @ExcelProperty("名称")
    private String name;
    @ExcelProperty("值")
    private String value;
    @ExcelProperty("编码")
    private String dictCode;

    @JsonFormat(timezone = "Asia/ShangHai",pattern = "yyy-MM-dd HH:mm:ss" )
    @DateTimeFormat(pattern = "yyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    private String createBy;

    @JsonFormat(timezone = "Asia/ShangHai",pattern = "yyy-MM-dd HH:mm:ss" )
    @DateTimeFormat(pattern = "yyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

    private String updateBy;

    private Integer delFlag;
    @TableField(exist = false)
    private List<SysDict> children;
}

2. 书写返回树状菜单方法

public class SysDictTreeTest{
    public List<SysDict> getAllList() {
        //从数据库中获取数据
        List<SysDict> sysDictList = sysDictMapper.selectList(new QueryWrapper<>());
        return getTreeDict(sysDictList);
    }
    private List<SysDict> getTreeDict(List<SysDict> list){
        List<SysDict> rootList = list.stream()
            .filter(f -> f.getParentId() == 0)
            .collect(Collectors.toList());
        list.removeAll(rootList);
        if(CollUtil.isNotEmpty(rootList)){
            for (SysDict sysDict : rootList) {
                setChildList(sysDict,list);
            }
        }
        return rootList;
    }

    private void setChildList(SysDict root,List<SysDict> list){
        List<SysDict> childList = list.stream()
            .filter(f -> f.getParentId().equals(root.getId()))
            .collect(Collectors.toList());
        list.removeAll(childList);
        root.setChildren(childList);
        if(CollUtil.isNotEmpty(childList)){
            for (SysDict sysDict : childList) {
                setChildList(sysDict,list);
            }
        }
    }
   
}

3. 抽离出来的通用类

1. 抽离公共类

通过反射书写一个通用类

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author eleven
 * @date 2022/4/15 10:51
 * @apiNote 返回层级菜单
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TreeMenuUtil<T> {
    /** 顶层节点的值 */
    private String rootValue;
    /** 对应父节点的子属性 */
    private String childKey;
    /** 父节点的属性值 */
    private String rootKey;
    /** 子菜单放置的字段 */
    private String childProperty;
    /** 要过滤的集合 */
    private List<T> list;
    
    /**
     * @apiNote 返回树状菜单
     * @return List
     */
    public List<T> rootMenu(){
        //判断rootValue是否为空,如果为空的话给rootValue赋值
        ifNullRootValueSetValue();
        //筛选出来顶级目录
        List<T> rootList = list.stream()
                .filter(item -> StrUtil.equals(rootValue, getValueByProperty(item, childKey)))
                .collect(Collectors.toList());
        //从数据集合中剔除顶层目录,减少后续遍历次数,加快速度
        list.removeAll(rootList);
        //如果list不为空的话则遍历赋值子菜单
        if (CollUtil.isNotEmpty(rootList)) {
            for (T t : rootList) {
                setChildren(t, list);
            }
            return rootList;
        }else{
            return list;
        }
    }

    /**
     * 判断rootKey是控制的话则赋值
     */
    private void ifNullRootValueSetValue(){
        if(StrUtil.isBlank(rootValue)){
            Set<String> rootValueSet = list.stream()
                    .map(m -> getValueByProperty(m, rootKey))
                    .collect(Collectors.toSet());
            Set<String> childValueSet = list.stream()
                    .map(m -> getValueByProperty(m, childKey))
                    .collect(Collectors.toSet());
            //将childKey(parentId)中的数据全部赋值过来
            Set<String> resultList = new HashSet<>();
            resultList.addAll(childValueSet);
            //算出childValueSet和rootValueSet的交集
            childValueSet.retainAll(rootValueSet);
            // 算出childValueSet 与交集不同的部分
            resultList.removeAll(childValueSet);
            //如果有差集的话,把第一个数据返回到rootValue中
            if(CollUtil.isNotEmpty(resultList)){
                rootValue = String.valueOf(resultList.toArray()[0]);
            }
        }
    }

    /**
     * 给父级菜单赋值子菜单
     * @param t         父菜单对象
     * @param list      所有数据
     */
    private void setChildren(T t,List<T> list){
        String childPropertyTypeName = getPropertyDescriptor(t, childProperty).getPropertyType().getName();
        Stream<T> childStream = list.stream()
                .filter(item -> isChild(t, item));
        Collection<T> childList = null;
        if(childPropertyTypeName.contains("Set")){
            childList = childStream.collect(Collectors.toSet());
            setValueByProperty(t, (Set<T>) childList);
        }else {
            childList = childStream.collect(Collectors.toList());
            setValueByProperty(t, (List<T>) childList);
        }
        list.removeAll(childList);
        if (CollUtil.isNotEmpty(childList)) {
            for (T item : childList) {
                setChildren(item, list);
            }
        }
    }

    /**
     * 判断当前对象是不是父级对象的子级
     * @param t     父对象
     * @param item  子对象
     * @return
     */
    private boolean isChild(T t,T item){
        String rootValue = getValueByProperty(t, rootKey);
        String childParentValue = getValueByProperty(item, childKey);
        return rootValue.equals(childParentValue);
    }

    /**
     * 通过属性获取属性的值
     * @param t         对象
     * @param key       属性
     * @return
     */
    private String getValueByProperty(T t,String key){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t,key);;
        try {
            Method keyMethod = keyProperty.getReadMethod();
            return String.valueOf(keyMethod.invoke(t));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 给子级属性赋值
     * @param t         对象
     * @param list      子菜单集合     
     */
    private void setValueByProperty(T t,Collection<T> list){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t, childProperty);
        //获取getCurrentPage()方法
        try {
            Method keyMethod = keyProperty.getWriteMethod();
            keyMethod.invoke(t,list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过反射获取中的属性
     * @param t     对象
     * @param key   属性
     * @return
     */
    private PropertyDescriptor getPropertyDescriptor(T t, String key){
        Class clazz = t.getClass();
        try {
            return new PropertyDescriptor(key, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}

2. 使用

@Override
public List<SysDict> getAllList() {
    /**
     * 使用构造方法给工具类赋值
     * @param rootValue     顶级菜单的父级值                 这里就是parentId的值 
     * @param childKey      实体类中对应父级菜单的属性        这里是parentId
     * @param rootKey       实体类中父级的属性               这里是id
     * @param childProperty 实体类中对应的放置子菜单的属性     这里是children
     * @param list          所有的树状菜单数据                     
     */
	return new TreeMenuUtil<SysDict>(null,
                                  "parentId",
                                  "id",
                                  "children", 
                                  sysDictMapper.selectList(new QueryWrapper<>()))
        	.rootMenu();
}

4. 其他相关连接

  1. hutool-树结构工具-TreeUtil
  2. 博主其他博客设置层级菜单