当前位置: 首页 > news >正文

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": []}]
}

相关文章:

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

前言 这篇文中&#xff0c;我一共会用两种方式来实现目录树的数据结构&#xff0c;两种写法逻辑是一样的&#xff0c;只是一种适合新手理解&#xff0c;一种看着简单明了但是对于小白不是很好理解。在这里我会很详细的讲解每一步代码&#xff0c;主要是方便新人看懂&#xff0…...

基于Uniswap V3的去中心化前端现货交易平台Oku正式登陆Moonbeam

波卡上的Uniswap v3合约由Moonbeam智能合约、Oku前端&#xff0c;以及Wormhole远程路由技术共同实现。 跨链互连应用的最佳去中心化开发平台Moonbeam宣布Uniswap现已正式登陆。此次是Uniswap产品作为一个主流的DEX首次涉足Polkadot生态。用户可以通过新的、易于使用的Oku界面与…...

leetcode 每日一题复盘(10.9~10.15)

leetcode 101 对称二叉树 这道题一开始想是用层序遍历,看每一层是否都对称,遇到一个问题就是空指针(子树为空)无法记录下来,同时会导致操作空指针的问题,因此需要修改入队条件,并用一个标志去表示空指针 vector<int>numv;for(int i0;i<size;i){TreeNode*frontque.fro…...

【云计算网络安全】DDoS 缓解解析:DDoS 攻击缓解策略、选择最佳提供商和关键考虑因素

文章目录 一、前言二、什么是 DDoS 缓解三、DDoS 缓解阶段四、如何选择 DDoS 缓解提供商4.1 网络容量4.2 处理能力4.3 可扩展性4.4 灵活性4.5 可靠性4.6 其他考虑因素4.6.1 定价4.6.2 所专注的方向 文末送书《数据要素安全流通》本书编撰背景本书亮点本书主要内容 一、前言 云…...

如何巧用AI智能技术,让文物不再“无人问津”?

文物是文化与传统的象征&#xff0c;而博物馆则是展现文物的载体。传统的博物馆监控体系只是利用摄像头进行监控&#xff0c;无法将人工智能融入其中&#xff0c;使其更加智能化、信息化。那么&#xff0c;如何将AI技术与传统视频监控相融合呢&#xff1f;TSINGSEE青犀智能分析…...

一天一八股——SSL/TLS协议

早期设计的http协议存在诸多的问题&#xff0c;SSL/TLS在http的基础上保证了数据的保密&#xff0c;验证和身份验证 https的保密性通过混合加密的方式保证&#xff0c;解决窃听问题https数据的完整性通过摘要算法保证&#xff0c;通过数字证书CA的方式进行数据来源和数据可靠性…...

SpringCloud学习笔记-Eureka服务的搭建

目录 1.首先引入依赖2.main中配置注解3.src/main/resources/application.yml配置文件 本文的主要工作是介绍如何搭建一个Eureka服务 1.首先引入依赖 pom文件中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring…...

css如何实现页面布局与五种实现方式

CSS布局实现的主要方式有以下几种&#xff1a; 一、盒模型布局&#xff1a;CSS中&#xff0c;每个元素都是一个盒子&#xff0c;包括内容、内边距、边框和外边距。通过设置盒子的属性&#xff08;如宽度、高度、内边距、边框、定位等&#xff09;&#xff0c;可以实现不同的布…...

cv2.split函数与cv2.merge函数

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

Vue--1.7watch侦听器(监视器)

作用&#xff1a;监视数据变化&#xff0c;执行一些业务逻辑或异步操作。 语法&#xff1a; 1.简单写法->简单类型数据&#xff0c;直接监视 const app new Vue({el: #app,data: {words:},watch:{words(newValue,oldValue){}}}) const app new Vue({el: #app,data: {obj…...

序列:全序关系

一个序列满足全序关系必须满足以下条件&#xff1a; 反对称性&#xff1a;若 a ≤ b a\le b a≤b&#xff0c;则 b ≥ a b\ge a b≥a传递性&#xff1a;若 a ≤ b a\le b a≤b 且 b ≤ c b\le c b≤c&#xff0c;则 a ≤ c a\le c a≤c完全性&#xff1a; a ≤ b a\le b …...

100M服务器能同时容纳多少人访问?

100M的服务器带宽能够同时容纳的用户访问量需要考虑以下几个关键因素: &#x1f449;1.单个用户的平均访问流量大小 这取决于网站内容,是否有多媒体等。一般文本类网站每用户每次访问在50-100KB。 &#x1f449;2.每个用户的平均访问页面 通常每次访问会打开多个页面 &…...

Javascript 笔记:函数调用与函数上下文

在 JavaScript 中&#xff0c;函数上下文通常指的是函数在执行时的当前对象的引用&#xff0c;这通常用 this 关键字表示。this 关键字在不同的执行上下文中可能引用到不同的对象。 1 全局上下文 当 this 关键字用在全局上下文&#xff08;不在任何函数内部&#xff09;&#…...

【WebService】C#搭建的标准WebService接口,在使ESB模版作为参数无法获取参数数据

一、问题说明 1.1 问题描述 使用C# 搭建WebService接口&#xff0c;并按照ESB平台人员的要求&#xff0c;将命名空间改为"http://esb.webservice",使用PostmanESB平台人员提供的入参示例进行测试时&#xff0c;callBussiness接口参数message始终为null。 以下是ES…...

Sqlserver关于tempdb临时数据库文件个数的最佳实践

官方文档 https://learn.microsoft.com/zh-cn/sql/relational-databases/databases/tempdb-database?viewsql-server-ver16 https://learn.microsoft.com/en-US/troubleshoot/sql/database-engine/performance/recommendations-reduce-allocation-contention 一般而言&#x…...

【Java】微服务——微服务介绍和Eureka注册中心

目录 1.微服务介绍2.服务拆分和远程调用2.1.提供者与消费者 3.Eureka注册中心3.1.Eureka的结构和作用3.2.Eureka的结构3.3.搭建Eureka服务3.3.1.引入eureka依赖3.3.2.编写配置文件 3.4.服务注册及拉1&#xff09;引入依赖2&#xff09;配置文件3&#xff09;启动多个user-servi…...

C++ virtual 虚函数 虚基类

https://blog.csdn.net/xbb123456rt/article/details/81986691 基类指针可以指向一个派生类对象&#xff0c;但派生类指针不能指向基类对象。 可以在基类中将被重写的成员函数设置为虚函数&#xff0c;其含义是&#xff1a;当通过基类的指针或者引用调用该成员函数时&#xf…...

redis分布式秒杀锁

-- 获取锁标识&#xff0c;是否与当前线程一致&#xff1f; if(redis.call(get, KEYS[1]) ARGV[1]) then-- 一致&#xff0c;删除return redis.call(del, KEYS[1]) end -- 不一致&#xff0c;直接返回 return 0package com.platform.lock;public interface ILock {/*** 获取锁…...

【Redis】String内部编码方式

String内部编码方式 int: 8个字节的长整型embstr: 小于等于39个字节的字符串raw: 大于39个字节的字符串...

川西旅游网系统-前后端分离(前台vue 后台element UI,后端servlet)

前台&#xff1a;tour_forword: 川西旅游网前端----前台 (gitee.com) 后台&#xff1a;tour_back: 川西旅游网-------后台 (gitee.com) 后端 &#xff1a;tour: 川西旅游网------后端 (gitee.com)...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释

以Module Federation 插件详为例&#xff0c;Webpack.config.js它可能的配置和含义如下&#xff1a; 前言 Module Federation 的Webpack.config.js核心配置包括&#xff1a; name filename&#xff08;定义应用标识&#xff09; remotes&#xff08;引用远程模块&#xff0…...

python打卡第47天

昨天代码中注意力热图的部分顺移至今天 知识点回顾&#xff1a; 热力图 作业&#xff1a;对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图&#xff0c;展示模…...