Java中实现树形菜单的两种方式

 更新时间:2023年09月26日 10:00:39   作者:我还是个孩子  
这篇文中,我一共会用两种方式来实现目录树的数据结构,两种写法逻辑是一样的,只是一种适合新手理解,一种看着简单明了但是对于小白不是很好理解,在这里我会很详细的讲解每一步代码,主要是方便新人看懂,弥补曾经自己学习过程中的苦恼,需要的朋友可以参考下

一、什么是目录结构?

就是在实际开发过程中,总会遇到菜单,或则是权限,这个时候就涉及到后端返回数据给前端的时候,不能一个集合把数据一股脑的全部扔给前端,总要把数据整理好,做成像书目录一样的结构返回给前端。就像以下图示一样

二、目录树结构实现写法

1、准备阶段

①创建数据表

PS:如果是练习可以不用创建数据库,数据全部通过java代码来创建也可以

CREATE TABLE permission_directory (
id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
parent_id int(11) NOT NULL DEFAULT '0' COMMENT '父目录ID',
menu_name varchar(255) NOT NULL COMMENT '菜单名称',
menu_level int(11) NOT NULL COMMENT '菜单等级',
route varchar(255) NOT NULL COMMENT '路由',
PRIMARY KEY (id) COMMENT '主键',
UNIQUE KEY parent_id (parent_id,menu_name,menu_level,route) COMMENT '唯一索引,包含父目录ID、菜单名称、菜单等级和路由'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '存储引擎为InnoDB,字符集为utf8';

②向表中插入数据

INSERT INTO permission_directory (parent_id, menu_name, menu_level, route) VALUES
(1, '首页', 0, '/index'),
(2, '系统设置', 0, '/user/manage'),
(3, '操作手册', 0, '/role/manage'),
(4, '菜单管理', 2, '/menu/manage'),
(5, '用户管理', 2, '/system/setting'),
(6, '日志管理', 3, '/log/manage'),
(7, '定时任务', 3, '/task/schedule'),
(8, 'API接口文档', 3, '/api/documentation'),
(9, '操作手册', 8, '/operation/manual');

③创建菜单对象PermissionDirectory

PS:这里我用了@Data注解,就不用封装属性了,如果没写@Data注解就把每个属性封装以下,也就是get()和set()方法

@Data
public class PermissionDirectory {
    @MyAnnotation("主键id")
    private int id;
    @MyAnnotation("父目录id")
    private int parentId;
    @MyAnnotation("菜单名称")
    private String menuName;
    @MyAnnotation("菜单等级")
    private int menuLevel;
    @MyAnnotation("路由")
    private String route;
}

④创建存储菜单对象PermissionDirectoryResVO

@Data
public class PermissionDirectoryResVO {
    @MyAnnotation("主键id")
    private Integer id;
    @MyAnnotation("父目录id")
    private Integer parentId;
    @MyAnnotation("菜单名称")
    private String menuName;
    @MyAnnotation("菜单等级")
    private Integer menuLevel;
    @MyAnnotation("路由")
    private String route;
    @MyAnnotation("用于存储当前目录下面的全部子集")
    private List<PermissionDirectoryResVO> authMenuList;
}

2、逻辑代码实现

这里关于如何去连接数据库啊等等一系列都省略了,关键就是目录树的逻辑讲解

①第一种写法

    public List<PermissionDirectoryResVO> searchMenu() {
        List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
        List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
        if (CollectionUtil.isNotEmpty(menuList)){
            List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> {
                PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
                BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO);
                return permissionDirectoryResVO;
            }).collect(Collectors.toList());
            pdr.forEach(e ->{
                List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
                e.setAuthMenuList(pdrList != null ? pdrList : null);
            });
            List<PermissionDirectoryResVO> parentNodes = pdr.stream().
                    filter(e -> e.getParentId().equals(0)).collect(Collectors.toList());
            directoryTree.addAll(parentNodes);
        }
        return directoryTree;
    }
    /**
     * 获取全部子集
     * @param id
     * @param list
     * @return
     */
    public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
        return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList());
    }
}

第一种写法代码详细解

第一步:创建存储最终结果数据的集合容器
    List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
第二步:获取需要整理成树状结构的所有数据
    List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
	PS:这里我是通过查询数据获取的数据,练习的话,可以new一些数据出来存入集合中就行了
第三步:判断获取的数据是否为空,如果为空的话就没有去整理成树结构的必要了,数据都没有
    if (CollectionUtil.isNotEmpty(menuList)){ .... }
	PS:这里我用的是糊涂类提供的方法进行判断,如果小白在写的过程中发现报错,找不到这个方法或则这个类就换一种写法
第四步:将获取的PermissionDirectory数据全部赋值给PermissionDirectoryResVO
     List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> {
     	PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
     	BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO);
     	return permissionDirectoryResVO;
     }).collect(Collectors.toList());
	 具体解释如下:
        menuList.stream():将menuList集合转换为一个流(Stream)
        map(PermissionDirectory -> {...}):这个简单理解就是循环menuList集合,然后遍历集合中的每一个PermissionDirectory元素
        BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO):将PermissionDirectory对象的属性值复制到permissionDirectoryResVO对象中。这样,authMenuResVO对象就具有了与AuthMenu对象相同的属性值。
        return permissionDirectoryResVO:将转换后的permissionDirectoryResVO对象作为结果返回给调用者。
        collect(Collectors.toList()):将处理后的流中的元素收集到一个新的列表中,并返回该列表
        因此,这段代码的作用是将原始列表menuList中的每个元素转换为AuthMenuResVO类型的对象,并将转换后的对象存储在一个新的列表permissionDirectoryResVO中。
第五步:写一个获取子集的方法体
        public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
        	return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList());
    	}
       具体解释如下:
           forEach(e -> {...}):是list对象的一个方法,用于遍历该列表(或集合)中的每个元素,并对每个元素执行一段操作。
           e -> {...}是一个Lambda表达式,表示对每个元素执行的操作,相当于e就是PermissionDirectoryResVO元素对象
           因此,这段代码就是通过传递一个主键id和一个PermissionDirectoryResVO集合对象参数,然后遍历循环PermissionDirectoryResVO对象集合,把每一个对象的父目录id和传递过来的参数id进行对比,如果父目录id等于参数id就把这个对象收集到新的集合中,最后作为参数返回。
第六步:遍历全部数据,利用递归思想,获取全部的子集
      	  pdr.forEach(e ->{
          	List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
            e.setAuthMenuList(pdrList != null ? pdrList : null);
           });
		具体解释如下:
            List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);这一步通过调用第五步写好的方法已经获取到了全部子集,就是说,如果所有数据一集目录有三个,分别是1、2、3,那么当循环完的时候会有3个pdrList集合,每个集合中分别装有1目录下的数据、2目录下的数据、3目录下的数据。
            当每一次循环的时候,都会对pdr集合中的元素进行一次判断,e.setAuthMenuList(pdrList != null ? pdrList : null);使用三目运算符,如果pdrList集合不为空就表示当前元素有子集,然把pdrList集合赋值给元素的authMenuList属性,如果为空就表示没有子集,赋值空就可以。
            当集合遍历完毕,数据情况看图①实例
第七步:获取所有顶点数据
           List<PermissionDirectoryResVO> parentNodes = pdr.stream().
           		filter(e -> e.getParentId().equals(0)).collect(Collectors.toList());
           directoryTree.addAll(parentNodes);
		   具体解释如下:
               判断pdr集合中父目录id为0的数据,然后赋值给新的parentNodes,最后把这个集合存进directoryTree集合容器中

图①

②第二种写法

    public List<PermissionDirectoryResVO> searchMenu() {
        List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
        // 获取全部数据
        List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
        // 创建存储PermissionDirectoryResVO对象的集合容器
        List<PermissionDirectoryResVO> pdr = new ArrayList<>();
        // 判断集合中数据是否为空,不为空进行树结构排列
        if (CollectionUtil.isNotEmpty(menuList)){
            // 遍历循环集合menuList元素赋值给pdr集合中元素对象,这里就是第一种写法的第四步
            for (PermissionDirectory permissionDirectory : menuList){
                PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
                permissionDirectoryResVO.setId(permissionDirectory.getId());
                permissionDirectoryResVO.setParentId(permissionDirectory.getParentId());
                permissionDirectoryResVO.setMenuName(permissionDirectory.getMenuName());
                permissionDirectoryResVO.setMenuLevel(permissionDirectory.getMenuLevel());
                permissionDirectoryResVO.setRoute(permissionDirectory.getRoute());
                pdr.add(permissionDirectoryResVO);
            }
        }
        // 遍历全部数据,利用递归思想,获取全部的子集,第一种写法的第六步
        for (PermissionDirectoryResVO e : pdr){
            List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
            e.setAuthMenuList(pdrList != null ? pdrList : null);
        }
        // 获取所有顶点数据
        for (PermissionDirectoryResVO e : pdr){
            if (e.getParentId().equals(0)){
                directoryTree.add(e);
            }
        }
        return directoryTree;
    }
    /**
     * 获取全部子集
     * @param id
     * @param list
     * @return
     */
    public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
        List<PermissionDirectoryResVO> pdr = new ArrayList<>();
        // 这里就是第一种写法的第五步
        for (PermissionDirectoryResVO per : list){
            if (per.getParentId().equals(id)){
                pdr.add(per);
            }
        }
        return pdr;
    }
}

最终结果

{
    "code": 200,
    "msg": "操作成功",
    "data": [
        {
            "id": 3,
            "parentId": 0,
            "menuName": "操作手册",
            "menuLevel": 1,
            "route": "/role/manage",
            "authMenuList": [
                {
                    "id": 8,
                    "parentId": 3,
                    "menuName": "API接口文档",
                    "menuLevel": 2,
                    "route": "/api/documentation",
                    "authMenuList": [
                        {
                            "id": 9,
                            "parentId": 8,
                            "menuName": "操作手册",
                            "menuLevel": 3,
                            "route": "/operation/manual",
                            "authMenuList": []
                        }
                    ]
                },
                {
                    "id": 7,
                    "parentId": 3,
                    "menuName": "定时任务",
                    "menuLevel": 2,
                    "route": "/task/schedule",
                    "authMenuList": []
                },
                {
                    "id": 6,
                    "parentId": 3,
                    "menuName": "日志管理",
                    "menuLevel": 2,
                    "route": "/log/manage",
                    "authMenuList": []
                }
            ]
        },
        {
            "id": 2,
            "parentId": 0,
            "menuName": "系统设置",
            "menuLevel": 1,
            "route": "/user/manage",
            "authMenuList": [
                {
                    "id": 5,
                    "parentId": 2,
                    "menuName": "用户管理",
                    "menuLevel": 2,
                    "route": "/system/setting",
                    "authMenuList": []
                },
                {
                    "id": 4,
                    "parentId": 2,
                    "menuName": "菜单管理",
                    "menuLevel": 2,
                    "route": "/menu/manage",
                    "authMenuList": []
                }
            ]
        },
        {
            "id": 1,
            "parentId": 0,
            "menuName": "首页",
            "menuLevel": 1,
            "route": "/index",
            "authMenuList": []
        }
    ]
}

以上就是Java中实现树形菜单的两种方式的详细内容,更多关于Java实现树形菜单的资料请关注脚本之家其它相关文章!

相关文章

  • Java实现为Word每一页设置不同图片水印的效果

    Java实现为Word每一页设置不同图片水印的效果

    Word中设置水印时,可加载图片设置为水印效果,但通常添加水印效果时,会对所有页面都设置成统一效果。所以本文为大家介绍了一个方法,可以实现对每一页或者某个页面设置不同的水印效果,需要的可以参考一下
    2022-02-02
  • SpringBoot Admin 使用指南(推荐)

    SpringBoot Admin 使用指南(推荐)

    这篇文章主要介绍了SpringBoot Admin 使用指南(推荐),Spring Boot Admin 是一个管理和监控你的 Spring Boot 应用程序的应用程序,非常具有实用价值,需要的朋友可以参考下
    2018-01-01
  • SpringSecurity退出功能实现的正确方式(推荐)

    SpringSecurity退出功能实现的正确方式(推荐)

    本文将介绍在Spring Security框架下如何实现用户的"退出"logout的功能。本文通过实例代码讲解的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-11-11
  • java面试散列表及树所对应容器类及HashMap冲突解决全面分析

    java面试散列表及树所对应容器类及HashMap冲突解决全面分析

    这篇文章主要介绍了java面试中的java散列表及树所对应容器类与HashMap冲突解决的问题总结,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-10-10
  • java设计模式策略模式图文示例详解

    java设计模式策略模式图文示例详解

    这篇文章主要为大家介绍了java设计模式策略模式图文示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Java super和this的对比及使用

    Java super和this的对比及使用

    这篇文章主要介绍了Java super和this的对比及使用的相关资料,java中this与super会经常在使用的时候混淆,需要的朋友可以参考下
    2017-08-08
  • Scala异常处理的方法深入分析

    Scala异常处理的方法深入分析

    Scala是一种多范式的编程语言,支持面向对象和函数式编程。Scala也支持异常处理,即在程序运行过程中发生意外或错误时,采取相应的措施
    2023-04-04
  • Java通过调用FFMPEG获取视频时长

    Java通过调用FFMPEG获取视频时长

    这篇文章主要为大家详细介绍了Java通过调用FFMPEG获取视频时长,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-04-04
  • SpringBoot整合Shiro实现权限控制的代码实现

    SpringBoot整合Shiro实现权限控制的代码实现

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理,今天通过本文给大家介绍SpringBoot整合Shiro实现权限控制的方法,感兴趣的朋友一起看看吧
    2021-07-07
  • java实现哈夫曼压缩的实例

    java实现哈夫曼压缩的实例

    这篇文章主要介绍了java实现哈夫曼压缩的实例的相关资料,需要的朋友可以参考下
    2017-07-07

最新评论