• Java中树形菜单的实现方式(超全详解!)


    前言

    这篇文中,我一共会用两种方式来实现目录树的数据结构,两种写法逻辑是一样的,只是一种适合新手理解,一种看着简单明了但是对于小白不是很好理解。在这里我会很详细的讲解每一步代码,主要是方便新人看懂,弥补曾经自己学习过程中的苦恼。提醒:如果第一种写法理解不了或则看不懂,可以看第二种写法,通过第二种写法去理解第一种的写法,两种写法逻辑是一样的。后面我也会详细去讲解。

    一、什么是目录结构?

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

    二、目录树结构实现写法

    1、准备阶段
    ①创建数据表

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

    1. CREATE TABLE permission_directory (
    2. id int(11NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    3. parent_id int(11NOT NULL DEFAULT '0' COMMENT '父目录ID',
    4. menu_name varchar(255NOT NULL COMMENT '菜单名称',
    5. menu_level int(11NOT NULL COMMENT '菜单等级',
    6. route varchar(255NOT NULL COMMENT '路由',
    7. PRIMARY KEY (id) COMMENT '主键',
    8. UNIQUE KEY parent_id (parent_id,menu_name,menu_level,route) COMMENT '唯一索引,包含父目录ID、菜单名称、菜单等级和路由'
    9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '存储引擎为InnoDB,字符集为utf8';
    ②向表中插入数据
    1. INSERT INTO permission_directory (parent_id, menu_name, menu_level, route) VALUES
    2. (1'首页'0'/index'),
    3. (2'系统设置'0'/user/manage'),
    4. (3'操作手册'0'/role/manage'),
    5. (4'菜单管理'2'/menu/manage'),
    6. (5'用户管理'2'/system/setting'),
    7. (6'日志管理'3'/log/manage'),
    8. (7'定时任务'3'/task/schedule'),
    9. (8'API接口文档'3'/api/documentation'),
    10. (9'操作手册'8'/operation/manual');
    ③创建菜单对象PermissionDirectory

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

    1. @Data
    2. public class PermissionDirectory {
    3.     @MyAnnotation("主键id")
    4.     private int id;
    5.     @MyAnnotation("父目录id")
    6.     private int parentId;
    7.     @MyAnnotation("菜单名称")
    8.     private String menuName;
    9.     @MyAnnotation("菜单等级")
    10.     private int menuLevel;
    11.     @MyAnnotation("路由")
    12.     private String route;
    13. }
    ④创建存储菜单对象PermissionDirectoryResVO
    1. @Data
    2. public class PermissionDirectoryResVO {
    3.     @MyAnnotation("主键id")
    4.     private Integer id;
    5.     @MyAnnotation("父目录id")
    6.     private Integer parentId;
    7.     @MyAnnotation("菜单名称")
    8.     private String menuName;
    9.     @MyAnnotation("菜单等级")
    10.     private Integer menuLevel;
    11.     @MyAnnotation("路由")
    12.     private String route;
    13.     @MyAnnotation("用于存储当前目录下面的全部子集")
    14.     private List authMenuList;
    15. }
    2、逻辑代码实现

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

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

    图①

    ②第二种写法
    1.     public List<PermissionDirectoryResVO> searchMenu() {
    2.         List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
    3.         
    4.         List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
    5.         
    6.         List<PermissionDirectoryResVO> pdr = new ArrayList<>();
    7.         
    8.         if (CollectionUtil.isNotEmpty(menuList)){
    9.             
    10.             for (PermissionDirectory permissionDirectory : menuList){
    11.                 PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
    12.                 permissionDirectoryResVO.setId(permissionDirectory.getId());
    13.                 permissionDirectoryResVO.setParentId(permissionDirectory.getParentId());
    14.                 permissionDirectoryResVO.setMenuName(permissionDirectory.getMenuName());
    15.                 permissionDirectoryResVO.setMenuLevel(permissionDirectory.getMenuLevel());
    16.                 permissionDirectoryResVO.setRoute(permissionDirectory.getRoute());
    17.                 pdr.add(permissionDirectoryResVO);
    18.             }
    19.         }
    20.         
    21.         for (PermissionDirectoryResVO e : pdr){
    22.             List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
    23.             e.setAuthMenuList(pdrList != null ? pdrList : null);
    24.         }
    25.         
    26.         for (PermissionDirectoryResVO e : pdr){
    27.             if (e.getParentId().equals(0)){
    28.                 directoryTree.add(e);
    29.             }
    30.         }
    31.         return directoryTree;
    32.     }
    33.     
    34.      * 获取全部子集
    35.      * @param id
    36.      * @param list
    37.      * @return
    38.      */
    39.     public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
    40.         List<PermissionDirectoryResVO> pdr = new ArrayList<>();
    41.         
    42.         for (PermissionDirectoryResVO per : list){
    43.             if (per.getParentId().equals(id)){
    44.                 pdr.add(per);
    45.             }
    46.         }
    47.         return pdr;
    48.     }
    49. }
    最终结果
    1. {
    2.     "code"200,
    3.     "msg""操作成功",
    4.     "data": [
    5.         {
    6.             "id"3,
    7.             "parentId"0,
    8.             "menuName""操作手册",
    9.             "menuLevel"1,
    10.             "route""/role/manage",
    11.             "authMenuList": [
    12.                 {
    13.                     "id"8,
    14.                     "parentId"3,
    15.                     "menuName""API接口文档",
    16.                     "menuLevel"2,
    17.                     "route""/api/documentation",
    18.                     "authMenuList": [
    19.                         {
    20.                             "id"9,
    21.                             "parentId"8,
    22.                             "menuName""操作手册",
    23.                             "menuLevel"3,
    24.                             "route""/operation/manual",
    25.                             "authMenuList": []
    26.                         }
    27.                     ]
    28.                 },
    29.                 {
    30.                     "id"7,
    31.                     "parentId"3,
    32.                     "menuName""定时任务",
    33.                     "menuLevel"2,
    34.                     "route""/task/schedule",
    35.                     "authMenuList": []
    36.                 },
    37.                 {
    38.                     "id"6,
    39.                     "parentId"3,
    40.                     "menuName""日志管理",
    41.                     "menuLevel"2,
    42.                     "route""/log/manage",
    43.                     "authMenuList": []
    44.                 }
    45.             ]
    46.         },
    47.         {
    48.             "id"2,
    49.             "parentId"0,
    50.             "menuName""系统设置",
    51.             "menuLevel"1,
    52.             "route""/user/manage",
    53.             "authMenuList": [
    54.                 {
    55.                     "id"5,
    56.                     "parentId"2,
    57.                     "menuName""用户管理",
    58.                     "menuLevel"2,
    59.                     "route""/system/setting",
    60.                     "authMenuList": []
    61.                 },
    62.                 {
    63.                     "id"4,
    64.                     "parentId"2,
    65.                     "menuName""菜单管理",
    66.                     "menuLevel"2,
    67.                     "route""/menu/manage",
    68.                     "authMenuList": []
    69.                 }
    70.             ]
    71.         },
    72.         {
    73.             "id"1,
    74.             "parentId"0,
    75.             "menuName""首页",
    76.             "menuLevel"1,
    77.             "route""/index",
    78.             "authMenuList": []
    79.         }
    80.     ]
    81. }
  • 相关阅读:
    初识网络编程
    基于Java+SpringBoot制作一个社区宠物登记小程序
    【算法】矩阵快速幂优化动态规划
    【设计模式】工厂模式(Factory Pattern)
    【C++11】左值引用右值引用,移动构造的使用
    RDD上的持久化操作
    01 _ 为什么要学习数据结构和算法?
    07 目标检测-YOLO的基本原理详解
    《论文阅读27》SuperGlue: Learning Feature Matching with Graph Neural Networks
    算法:数组中的最大差值---“打擂台法“
  • 原文地址:https://blog.csdn.net/weixin_54542328/article/details/133698781