树形菜单构建工具类(递归写法)
1. 工具类源码
模型定义,这里受Spring Security的UserService启发,给model设置接口.想要使用此工具类的话,实现此接口即可.
package xyz.yq56.easytool.abs;
import java.util.List;
/**
* @author yi qiang
* @date 2021/10/5 14:35
*/
public interface TreeNode {
/**
* 获取id
*
* @return id
*/
String getId();
/**
* 获取父类id
*
* @return 父类id
*/
String getPid();
/**
* 获取子类集合
*
* @return 子类集合
*/
<T extends TreeNode> List<T> getChildren();
/**
* 设置子类集合
*
* @param children 子类集合
*/
<T extends TreeNode> void setChildren(List<T> children);
}
工具类源码:
package xyz.yq56.easytool.utils.recursion;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import xyz.yq56.easytool.abs.TreeNode;
import xyz.yq56.easytool.utils.collection.CollectUtil;
/**
* @author yi qiang
* @date 2021/10/5 14:33
*/
public class RecursionUtil {
private RecursionUtil() {}
/**
* 使用时,只要node实现TreeNode接口即可实现树形菜单
* <br> 实现逻辑很简单:
* <br> 找到子类,并给children设值
* <br> 由于子类可能还有子类,故在子类内部递归调用,找到子类的子类
*
* @param list 树形结构的扁平数据
* @param node 根节点
* @param <T> 泛型,只要实现TreeNode接口即可生成树形结构
*/
public static <T extends TreeNode> void buildTree(List<T> list, T node) {
if (CollectUtil.isEmpty(list)) {
return;
}
List<T> children = new ArrayList<>();
for (T menu : list) {
if (Objects.equals(menu.getPid(), node.getId())) {
children.add(menu);
buildTree(getRemainList(list, menu.getPid()), menu);
}
}
if(!CollectUtil.isEmpty(children)){
node.setChildren(children);
}
}
/**
* 获取到的剩余集合会越来越少,避免堆栈溢出
*
* @param list list
* @param pid pid
* @return 剩余集合
*/
private static <T extends TreeNode> List<T> getRemainList(List<T> list, String pid) {
return CollectUtil.filter(list,item -> !Objects.equals(item.getPid(), pid));
}
}
2. 使用示例
实现TreeNode接口
package xyz.yq56.sm.module.menu.model;
import java.util.List;
import lombok.Data;
import xyz.yq56.easytool.abs.TreeNode;
/**
* @author yi qiang
* @date 2021/10/4 0:03
*/
@Data
public class MenuVo implements TreeNode {
private String id;
private String pid;
private String name;
private String path;
private String icon;
private List<MenuVo> children;
@Override
public <T extends TreeNode> void setChildren(List<T> list) {
this.children = (List<MenuVo>) list;
}
}
service层代码: 直接使用RecursionUtil.buildTree方法即可
package xyz.yq56.sm.module.menu.service.impl;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.yq56.easytool.utils.collection.CollectUtil;
import xyz.yq56.easytool.utils.nvll.NullUtil;
import xyz.yq56.easytool.utils.recursion.RecursionUtil;
import xyz.yq56.sm.module.menu.model.Menu;
import xyz.yq56.sm.module.menu.model.MenuVo;
import xyz.yq56.sm.module.menu.service.MenuBizService;
import xyz.yq56.sm.module.menu.service.MenuService;
/**
* @author yi qiang
* @date 2021/10/4 0:18
*/
@Service
public class MenuBizServiceImpl implements MenuBizService {
@Autowired
private MenuService menuService;
@Override
public MenuVo getMenu(String pid) {
//拿到菜单原始数据
List<Menu> list = menuService.list();
//拿到菜单原始数据,转化为vo集合
List<MenuVo> menuVoList = CollectUtil.mapToOtherList(list, item -> {
MenuVo menuVo = new MenuVo();
BeanUtils.copyProperties(item, menuVo);
return menuVo;
});
//如果指定了特定节点,那么特定节点为根节点,没有的话就默认null的是根节点
Menu byId = menuService.getById(pid);
MenuVo menuVo = new MenuVo();
NullUtil.apply(byId, item -> {
BeanUtils.copyProperties(item, menuVo);
return null;
});
//构建以menuVo为根节点的树形结构
RecursionUtil.buildTree(menuVoList, menuVo);
return menuVo;
}
}
controller
/**
* @author yi qiang
* @date 2021/10/4 0:14
*/
@RestController
@RequestMapping("/menu/biz/")
public class MenuBizController {
@Autowired
MenuBizService menuBizService;
@GetMapping("getMenu")
public MenuVo getMenu(String pid) {
return menuBizService.getMenu(pid);
}
}
请求效果
不指定特定节点时,以null为根节点.日常使用,可以直接拿根节点的children作为菜单也可.
http://localhost:8080/menu/biz/getMenu
{
"code": 200,
"data": {
"id": null,
"pid": null,
"name": null,
"path": null,
"icon": null,
"children": [
{
"id": "1445371079417503745",
"pid": null,
"name": "用户管理",
"path": "/user",
"icon": "el-icon-user",
"children": [
{
"id": "1445372598120456194",
"pid": "1445371079417503745",
"name": "用户信息",
"path": "/user_info",
"icon": "el-icon-s-custom",
"children": null
},
{
"id": "1445405355609530370",
"pid": "1445371079417503745",
"name": "用户列表",
"path": "/users",
"icon": "el-icon-s-custom",
"children": null
}
]
},
{
"id": "1445372340355309569",
"pid": null,
"name": "菜单管理",
"path": "/menu",
"icon": "el-icon-menu",
"children": null
}
]
},
"msg": "success",
"date": "2021-10-05T17:38:36.153+00:00"
}
指定特定节点.比如我只想取以上用户管理的菜单,那么拿到用户管理的id传入即可.
http://localhost:8080/menu/biz/getMenu?pid=1445371079417503745
{
"code": 200,
"data": {
"id": "1445371079417503745",
"pid": null,
"name": "用户管理",
"path": "/user",
"icon": "el-icon-user",
"children": [
{
"id": "1445372598120456194",
"pid": "1445371079417503745",
"name": "用户信息",
"path": "/user_info",
"icon": "el-icon-s-custom",
"children": null
},
{
"id": "1445405355609530370",
"pid": "1445371079417503745",
"name": "用户列表",
"path": "/users",
"icon": "el-icon-s-custom",
"children": null
}
]
},
"msg": "success",
"date": "2021-10-05T17:41:30.870+00:00"
}
数据库原始数据
前端展示(Vue)
3 结语
不知道怎么构建树形菜单的小伙伴可以看下这个工具类,希望可以给一部分朋友一点启示.有帮助的话,请点赞或评论,谢谢
4 补充
CollectUtil: 封装了一些常见的集合操作
package xyz.yq56.easytool.utils.collection;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.springframework.util.CollectionUtils;
import xyz.yq56.easytool.utils.nvll.NullUtil;
/**
* @author yi qiang
* @date 2021/9/19 22:54
*/
public class CollectUtil extends CollectionUtils {
/**
* <br> 示例用法: toMap(list, User::getId, User::getName, (v1, v2) -> v1 + "," + v2)
* <br> [{"id":"1","name":"yq"},{"id":"1","name":"yw"}]
* <br> toMap映射结果 ==> {1=yq,yw}
*
* @param list 待处理list
* @param keyMapper key映射规则,例如 User::getId
* @param valueMapper Value映射规则,例如 User::getName
* @param mergeFunction merge规则,当出现key相同时,前后的value怎么取舍,例如: (v1,v2)-> v1+","+v2
* @param <T> list中的类型,比如User
* @param <K> key类型,比如String
* @param <U> value类型,比如String
* @return map
*/
public static <T, K, U> Map<K, U> toMap(List<T> list
, Function<? super T, ? extends K> keyMapper
, Function<? super T, ? extends U> valueMapper
, BinaryOperator<U> mergeFunction) {
if (NullUtil.isEmpty(list)) {
return Collections.emptyMap();
}
return list.stream().collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
}
/**
* 映射为其他list
*
* @param list 原list
* @param mapper 映射关系
* @param <T> 原类型
* @param <R> 映射类型
* @return 映射集合
*/
@Nullable
public static <T, R> List<R> mapToOtherList(List<T> list, Function<? super T, ? extends R> mapper) {
return NullUtil.apply(list, i -> i.stream().map(mapper).collect(Collectors.toList()));
}
/**
* @param list 原list
* @param predicate 过滤
* @param <T> 原类型
* @return 过滤后的list
*/
@Nullable
public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
return NullUtil.apply(list, i -> i.stream().filter(predicate).collect(Collectors.toList()));
}
/**
* 集合的大小
* <br> 已判空
*
* @param collection 集合
* @param <T> 泛型
* @return 集合大小
*/
public static <T> int size(Collection<T> collection) {
if (CollectionUtils.isEmpty(collection)) {
return 0;
}
return collection.size();
}
/**
* 分组统计
*/
public static <T, K> Map<K, Long> groupCount(List<T> list, Function<? super T, ? extends K> classifier) {
if (CollectUtil.isEmpty(list) || classifier == null) {
return Collections.emptyMap();
}
return list.stream().collect(Collectors.groupingBy(classifier, Collectors.counting()));
}
}
NullUtil: 封装了一些与Null相关的操作
package xyz.yq56.easytool.utils.nvll;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* 空值处理工具类
*
* @author yi qiang
* @date 2021/9/17 0:32
*/
public class NullUtil {
private NullUtil() {
}
/**
* 如果字符串为空值或者null,那么返回空值
*
* @param str 字符串
* @return 不会为null
*/
public static String emptyIfNull(String str) {
return StringUtils.isEmpty(str) ? "" : str;
}
/**
* 如果集合为空值或者null,那么返回空集合
*
* @param list 集合
* @return 不会为null
*/
public static <T> List<T> emptyIfNull(List<T> list) {
return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
}
/**
* 如果集合为空值或者null,那么返回空集合
*
* @param set 集合
* @return 不会为null
*/
public static <T> Set<T> emptyIfNull(Set<T> set) {
return CollectionUtils.isEmpty(set) ? Collections.emptySet() : set;
}
/**
* 如果集合为空值或者null,那么返回空集合
*
* @param map 集合
* @return 不会为null
*/
public static <K, V> Map<K, V> emptyIfNull(Map<K, V> map) {
return CollectionUtils.isEmpty(map) ? Collections.emptyMap() : map;
}
/**
* 执行function,t为null则返回null
*
* @param t 主体
* @param function 函数
* @param <T> 入参
* @param <R> 结果
* @return 执行结果
*/
public static <T, R> R apply(T t, Function<T, R> function) {
return t == null ? null : function.apply(t);
}
/**
* t为null则返回null
* u为null执行第一种情况,u不为null执行第二种情况
*
* @param t 主体
* @param function 函数
* @param <T> 入参
* @param <R> 结果
* @return 执行结果
*/
public static <T, U, R> R apply(T t, U u, Function<T, R> function, BiFunction<T, U, R> biFunction) {
if (t == null) {
return null;
}
return u == null ? function.apply(t) : biFunction.apply(t, u);
}
/**
* 返回当前对象是否为空
*
* @param obj 对象
* @return 是否为空
*/
public static boolean isNull(Object obj) {
return obj == null;
}
/**
* 只要有一个为null,那就返回true
*
* @param objs objs 待判断集合
* @return 是否为null
*/
public static boolean isAndNull(Object... objs) {
//只要有一个为null,那就为true
for (Object obj : objs) {
if (obj == null) {
return true;
}
}
return false;
}
/**
* 只要有一个不为null,那就返回false
*
* @param objs 待判断集合
* @return 是否为null
*/
public static boolean isOrNull(Object... objs) {
for (Object obj : objs) {
if (obj != null) {
return false;
}
}
return true;
}
/**
* 返回当前集合类是否为空
*
* @param collection 集合类
* @return 集合类
*/
public static boolean isEmpty(Collection<?> collection) {
return CollectionUtils.isEmpty(collection);
}
/**
* 返回当前Map是否为空
*
* @param map map
* @return 是否为空
*/
public static boolean isEmpty(Map<?, ?> map) {
return CollectionUtils.isEmpty(map);
}
/**
* 返回当前数组是否为空
*
* @param array 数组
* @return 是否为空
*/
public static boolean isEmpty(Object[] array) {
return ArrayUtils.isEmpty(array);
}
public static <E, U> Optional<U> map(E e, Function<? super E, ? extends U> mapper) {
return Optional.ofNullable(e).map(mapper);
}
public static <E, U> U map(E e, Function<? super E, U> mapper, U defaultValue) {
return map(e, mapper).orElse(defaultValue);
}
public static void main(String[] args) {
System.out.println(NullUtil.map(null, item -> new Date(), 4));
}
}