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. 其他相关连接
- hutool-树结构工具-TreeUtil
- 博主其他博客设置层级菜单
- 设置树状菜单与扁平化Map菜单