Java构建树形菜单工具类,递归写法

树形菜单构建工具类(递归写法)

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));
    }

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值