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

前言

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

一、什么是目录结构?

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

二、目录树结构实现写法

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数据全部赋值给PermissionDirectoryResVOList<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();List<PermissionDirectoryResVO> pdr = new ArrayList<>();if (CollectionUtil.isNotEmpty(menuList)){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": []}]
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/98481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0开始python学习-31.selenium 文本输入框、下拉选择框、文件上传、时间插件选择元素定位

目录 1. 纯文本输入框 2. 存在默认值的文本输入 3. 下拉选择框 4. 输入后下拉选择框 5. 文件上传 6. 时间插件 1. 纯文本输入框 driver.find_element(By.XPATH,/html/body/div[2]/td[2]/input).send_keys(测试名称) 2. 存在默认值的文本输入 注意&#xff1a; 1. 这种存…

AQS内部的体系架构

AQS本质上是一个双向队列&#xff0c;加一个状态位state。内部靠Node节点形成队列。 AQS由state和CLH变体的虚拟双端队列组成。 AQS的内部类Node类 属性说明&#xff1a; 内部结构&#xff1a;

上门按摩小程序|同城上门按摩软件开发|上门按摩系统;

上门按摩小程序的开发具有许多优势&#xff0c;下面就给大家介绍下按摩小程序功能: 上门按摩小程序的优势 方便快捷&#xff1a;上门按摩小程序提供在线预约服务&#xff0c;用户可以通过手机随时随地预约按摩师上门服务&#xff0c;避免了传统预约方式的繁琐和不确定性。 个性…

TCP/IP网络协议通信函数接口

创建套接字函数 socket 【头文件】 #include <sys/types.h> #include <sys/socket.h> 【函数原型】 int socket(int domain, int type, int protocol); 【函数功能】 socket 函数创建一个通信端点&#xff0c;并返回一个引用该端点的文件描述符&#xff0c;…

IDEA2021创建Web项目配置Tomcat

1.新建一个普通的项目。 2.右键新建的项目&#xff0c;选择添加框架支持 3.勾选web application 4.在WEB-INF里创建lib和classes文件夹 5.file-project structure-modules-paths&#xff0c;选择use module compile output path&#xff0c;将output path和test output path的路…

基于FPGA的I2C读写EEPROM

文章目录 前言一、I2C协议1.1 I2C协议简介1.2 物理层1.3 协议层 二、EEPROM2.1 型号及硬件规格2.2 各种读写时序 三、状态机设计四、项目源码&#xff1a;五、实现效果参考资料 前言 本次项目所用开发板FPGA芯片型号为&#xff1a;EP4CE6F17C8 EEPROM芯片型号为&#xff1a;24L…

Linux CentOS7 yum仓库

在windows下安装一个软件很轻松&#xff0c;只要双击setup或者.exe的文件&#xff0c;安装提示连续“下一步”即可&#xff0c;然而linux系统下安装一个软件似乎并不那么轻松&#xff0c;因为我们不是在图形界面下。 本文我们将讨论如何在linux下安装一个软件。 一、linux软件…

混淆技术研究笔记(二)yGuard入门

yGuard官方文档地址&#xff1a;https://yworks.github.io/yGuard/index.html yGuard官方文档包含了比较全面的内容&#xff0c;由于文档是英文的&#xff0c;而且文档翻译后的浏览效果不是特别好&#xff0c;所以看文档入门有点难度。 这个系列的重点是混淆&#xff0c;所以…

4.物联网射频识别,RFID开发【智能门禁项目】

补充&#xff1a;学习路径 一。项目介绍及需求分析 1.酒店智能门禁使用场景介绍 1.客人入住 客人在前台办理入住手续&#xff0c;前台管理员通过门禁管理系统为客户开一张门禁卡 客户持卡到相应客房&#xff0c;用IC 卡刷卡开门 客人过了入住时间后&#xff0c;卡自动失效&a…

Linux自用笔记

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Linux相关 ✨特色专栏&#xff1a; My…

【网络安全 ---- 靶场搭建】凡诺企业网站管理系统靶场详细搭建过程(asp网站,练习sql注入)

一&#xff0c;资源下载 百度网盘资源下载链接&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com…

HDMI简介

VGA接口 VGA传输红绿蓝模拟信号和同步信号。因传输的模拟信号&#xff0c;易受干扰&#xff0c;因此&#xff0c;在高分辨率下字体容易虚&#xff0c;信号线长的话&#xff0c;图像有拖尾现象。目前一些显示器已经不带VGA接口&#xff0c;取而代之的是HDMI和DP接口。 如下图所示…

Python元组解密:不可变的数据之美

更多资料获取 &#x1f913; 作者主页&#xff1a;涛哥聊Python &#x1f4da; 个人网站&#xff1a;涛哥聊Python 元组是Python中一种有用的数据类型&#xff0c;用于存储不可变的有序集合。 本文将带您深入了解Python元组&#xff0c;包括定义、特点、创建、基本操作、不可…

第86步 时间序列建模实战:Transformer回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们介绍Transformer回归。 同样&#xff0c;这里使用这个数据&#xff1a; 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Sy…

机器学习必修课 - 交叉验证 Cross-Validation

想象一下你有一个包含5000行数据的数据集。通常情况下&#xff0c;你会将约20%的数据保留作为验证数据集&#xff0c;即1000行。但这会在确定模型得分时引入一些随机性。也就是说&#xff0c;一个模型可能在一组1000行数据上表现良好&#xff0c;即使在另一组1000行数据上表现不…

cv2.split函数与cv2.merge函数

split函数用于图像BGR通道的分离 merge函数用于可将分开的图像通道合并到一起 1.split函数的使用 这是原图&#xff0c;我们使用split函数对其三个通道进行分离。 注意&#xff1a;split函数分离通道的顺序是B、G、R。 以下方法是将三个通道的值都设置为与某一个通道相同。…

【Mysql】 blob 转text

有个数据表字段存储的字段类型是blob&#xff0c;想查看字段内容。 blob是二进制的无法直接查看怎么办&#xff1f; 写sql&#xff0c;blob 转text SELECT CONVERT(content USING utf8) FROM article_content ; 我想把原来content字段完全转成text 新建 text 类型字段conten…

k8s-8 ingress-nginx

nodeport 默认端口 nodeport默认端口是30000-32767&#xff0c;超出会报错 添加如下参数&#xff0c;端口范围可以自定义 externalname ingress-nginx 通过一个外部的vip 地址 访问到集群内的多个service 一种全局的、为了代理不同后端 Service 而设置的负载均衡服务&…

uniapp:swiper-demo效果

单元格轮播 <swiper class"swiper1" :circular"true" :autoplay"true" interval"3000" previous-margin"195rpx" next-margin"195rpx"><swiper-item v-for"(item,index) in 5" :key"inde…

虹科方案 | 车载以太网解决方案

全文导读&#xff1a;针对车载以太网&#xff0c;虹科为您提供转换器/交换机/捕捉模块/测试分析软件等相关专业工具、一体化培训以及“交钥匙”的解决方案&#xff0c;助力您的车载以太网数据传输、远程诊断、及时检测等项目。 文章目录 一、车载以太网的重要性&#xff1f;二、…