Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)
Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)

我们常常会遇到这样一个问题,就是前端要实现的样式是一个级联菜单或者是下拉树,如图


这样的数据接口是怎么实现的呢,是什么样子的呢?
我们可以看看 Elemui中的假数据
<el-cascader :options="options" clearable></el-cascader><script>export default {data() {return {options: [{value: 'zhinan',label: '指南',children: [{value: 'shejiyuanze',label: '设计原则',children: [{value: 'yizhi',label: '一致'}, {value: 'fankui',label: '反馈'}, {value: 'xiaolv',label: '效率'}, {value: 'kekong',label: '可控'}]}, {value: 'daohang',label: '导航',children: [{value: 'cexiangdaohang',label: '侧向导航'}, {value: 'dingbudaohang',label: '顶部导航'}]}]}, {value: 'zujian',label: '组件',children: [{value: 'basic',label: 'Basic',children: [{value: 'layout',label: 'Layout 布局'}, {value: 'color',label: 'Color 色彩'}, {value: 'typography',label: 'Typography 字体'}, {value: 'icon',label: 'Icon 图标'}, {value: 'button',label: 'Button 按钮'}]}, {value: 'form',label: 'Form',children: [{value: 'radio',label: 'Radio 单选框'}, {value: 'checkbox',label: 'Checkbox 多选框'}, {value: 'input',label: 'Input 输入框'}, {value: 'input-number',label: 'InputNumber 计数器'}, {value: 'select',label: 'Select 选择器'}, {value: 'cascader',label: 'Cascader 级联选择器'}, {value: 'switch',label: 'Switch 开关'}, {value: 'slider',label: 'Slider 滑块'}, {value: 'time-picker',label: 'TimePicker 时间选择器'}, {value: 'date-picker',label: 'DatePicker 日期选择器'}, {value: 'datetime-picker',label: 'DateTimePicker 日期时间选择器'}, {value: 'upload',label: 'Upload 上传'}, {value: 'rate',label: 'Rate 评分'}, {value: 'form',label: 'Form 表单'}]}, {value: 'data',label: 'Data',children: [{value: 'table',label: 'Table 表格'}, {value: 'tag',label: 'Tag 标签'}, {value: 'progress',label: 'Progress 进度条'}, {value: 'tree',label: 'Tree 树形控件'}, {value: 'pagination',label: 'Pagination 分页'}, {value: 'badge',label: 'Badge 标记'}]}, {value: 'notice',label: 'Notice',children: [{value: 'alert',label: 'Alert 警告'}, {value: 'loading',label: 'Loading 加载'}, {value: 'message',label: 'Message 消息提示'}, {value: 'message-box',label: 'MessageBox 弹框'}, {value: 'notification',label: 'Notification 通知'}]}, {value: 'navigation',label: 'Navigation',children: [{value: 'menu',label: 'NavMenu 导航菜单'}, {value: 'tabs',label: 'Tabs 标签页'}, {value: 'breadcrumb',label: 'Breadcrumb 面包屑'}, {value: 'dropdown',label: 'Dropdown 下拉菜单'}, {value: 'steps',label: 'Steps 步骤条'}]}, {value: 'others',label: 'Others',children: [{value: 'dialog',label: 'Dialog 对话框'}, {value: 'tooltip',label: 'Tooltip 文字提示'}, {value: 'popover',label: 'Popover 弹出框'}, {value: 'card',label: 'Card 卡片'}, {value: 'carousel',label: 'Carousel 走马灯'}, {value: 'collapse',label: 'Collapse 折叠面板'}]}]}, {value: 'ziyuan',label: '资源',children: [{value: 'axure',label: 'Axure Components'}, {value: 'sketch',label: 'Sketch Templates'}, {value: 'jiaohu',label: '组件交互文档'}]}]}}}
</script>
可以看见,这样的数据我们直接用SQL查出来是会损耗SQL性能的,我们这里展示如何在业务层做数据处理,实现树结构,这里就以若依的菜单数据为例,做一个基本演示!
查询数据我们分为两种,一种是获取指定的菜单,一种是获取全部的,获取指定的菜单我们需要写一个递归SQL,比如

我们如果获取全部那就是正常的查询所以,但是只要目录管理下面的菜单结构,不要其他的,那么这个SQL就是这样的:
如果想从sys_menu表中查询并获取菜单树的数据,可以使用递归查询。以下是一个基于MySQL的示例查询,该查询假设每个记录都有唯一的menu_id标识符和parent_id表示父菜单ID:
WITH RECURSIVE MenuCTE AS (SELECT menu_id,menu_name,parent_id,order_num,path,component,query,is_frame,is_cache,menu_type,visible,status,perms,icon,create_by,create_time,update_by,update_time,remarkFROM sys_menuWHERE parent_id = 0 -- 根节点的条件UNION ALLSELECT m.menu_id,m.menu_name,m.parent_id,m.order_num,m.path,m.component,m.query,m.is_frame,m.is_cache,m.menu_type,m.visible,m.status,m.perms,m.icon,m.create_by,m.create_time,m.update_by,m.update_time,m.remarkFROM sys_menu mJOIN MenuCTE cte ON m.parent_id = cte.menu_id
)
SELECT * FROM MenuCTE;
这个查询使用了MySQL的递归CTE(Common Table Expressions)功能,通过WITH RECURSIVE来逐级查询父菜单与子菜单的关系。查询结果包含了所有菜单及其层次结构关系。
我们分析一下这个SQL
这是一个使用递归CTE(Common Table Expressions)的SQL查询,用于获取具有层次结构关系的菜单数据。以下是对查询各部分的解释:
-
WITH RECURSIVE MenuCTE AS: 这是一个递归CTE的开始,MenuCTE是一个临时表名。递归CTE用于递归地查询表中的数据。 -
SELECT ... FROM sys_menu WHERE parent_id = 0: 这是递归CTE的初始查询部分,它选择根节点(parent_id = 0)的菜单记录。 -
UNION ALL: 这是联结两个查询结果集的关键字,它将上述初始查询结果与后续递归查询的结果联结在一起。 -
SELECT ... FROM sys_menu m JOIN MenuCTE cte ON m.parent_id = cte.menu_id: 这是递归查询的部分,通过连接sys_menu表自身,并使用递归关系m.parent_id = cte.menu_id来获取每个菜单的子菜单。 -
最后,整个递归CTE的最后部分是
SELECT * FROM MenuCTE,它选择了所有递归CTE的结果,包括根节点和其下的所有子节点。
该查询的结果是包含所有菜单数据的表,每一行都表示一个菜单项,具有其父菜单的引用关系,形成了一个层次结构。这对于表示树形结构的数据非常有用,例如用于构建具有层次关系的菜单系统。
当然,毫无疑问,获取出来的数据就是普通列表,下面我们就进行处理:
public static List<SysMenu> buildMenuTree(List<SysMenu> menuList) {Map<Long, SysMenu> menuMap = new HashMap<>();// 创建一个菜单ID到菜单对象的映射for (SysMenu menu : menuList) {menuMap.put(menu.getMenuId(), menu);}// 构建菜单树List<SysMenu> menuTree = new ArrayList<>();for (SysMenu menu : menuList) {Long parentId = menu.getParentId();if (parentId != null && menuMap.containsKey(parentId)) {SysMenu parentMenu = menuMap.get(parentId);parentMenu.getChildren().add(menu);} else {menuTree.add(menu); // 没有父菜单或父菜单未找到,将其作为根节点添加到树中}}return menuTree;}
这段 Java 代码是一个用于构建菜单树的方法,它接受一个包含 SysMenu 对象的列表作为输入,然后返回一个构建好的菜单树的列表。
让我对这段代码进行详细解读:
-
创建映射表:
Map<Long, SysMenu> menuMap = new HashMap<>();在这里,创建了一个
HashMap对象menuMap,用于将菜单ID映射到相应的SysMenu对象。 -
建立映射关系:
for (SysMenu menu : menuList) {menuMap.put(menu.getMenuId(), menu); }通过遍历输入的菜单列表
menuList,将每个菜单的menuId与对应的SysMenu对象建立映射关系。 -
构建菜单树:
List<SysMenu> menuTree = new ArrayList<>(); for (SysMenu menu : menuList) {Long parentId = menu.getParentId();if (parentId != null && menuMap.containsKey(parentId)) {SysMenu parentMenu = menuMap.get(parentId);parentMenu.getChildren().add(menu);} else {menuTree.add(menu); // 没有父菜单或父菜单未找到,将其作为根节点添加到树中} }遍历菜单列表
menuList,对于每个菜单,检查其parentId是否存在并且在menuMap中有对应的父菜单。如果是,将当前菜单添加到其父菜单的子菜单列表children中;如果不是,说明当前菜单是根节点,将其添加到menuTree中。 -
返回菜单树:
return menuTree;返回构建好的菜单树列表
menuTree。
这样,该方法将输入的扁平的菜单列表转换为一个带有层次结构的菜单树,其中每个菜单节点都包含了其子菜单的引用。这种结构更适合在用户界面上展示树形菜单。
这是我们自己写的,然后看看人家若依自己的,用到了递归:
/*** 构建前端所需要树结构** @param menus 菜单列表* @return 树结构列表*/public List<SysMenu> buildMenuTree(List<SysMenu> menus) {List<SysMenu> returnList = new ArrayList<SysMenu>();List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext(); ) {SysMenu menu = (SysMenu) iterator.next();// 如果是顶级节点, 遍历该父节点的所有子节点if (!tempList.contains(menu.getParentId())) {recursionFn(menus, menu);returnList.add(menu);}}if (returnList.isEmpty()) {returnList = menus;}return returnList;}/*** 递归列表** @param list 分类表* @param t 子节点*/private void recursionFn(List<SysMenu> list, SysMenu t) {// 得到子节点列表List<SysMenu> childList = getChildList(list, t);t.setChildren(childList);for (SysMenu tChild : childList) {if (hasChild(list, tChild)) {recursionFn(list, tChild);}}}
这段源码是一个用于构建菜单树的方法,输入是一个 List<SysMenu>,表示扁平结构的菜单列表,输出是一个构建好的菜单树。
让我对这段源码进行详细解读:
-
初始化:
List<SysMenu> returnList = new ArrayList<SysMenu>(); List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());returnList是最终返回的菜单树列表。tempList是将menus列表中的菜单ID提取出来的列表。
-
遍历菜单列表:
for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext(); ) {SysMenu menu = (SysMenu) iterator.next();使用迭代器遍历
menus列表中的每个菜单。 -
判断是否为顶级节点:
if (!tempList.contains(menu.getParentId())) {如果当前菜单的
parentId不在tempList中,说明它是顶级节点。 -
递归构建子节点:
recursionFn(menus, menu);调用
recursionFn方法递归构建当前顶级节点的子节点。 -
将当前节点添加到返回列表:
returnList.add(menu);将当前菜单节点添加到最终返回的菜单树列表中。
-
处理空的返回列表:
if (returnList.isEmpty()) {returnList = menus; }如果最终返回的菜单树列表为空,说明输入的菜单列表本身就是一个树,直接将其作为返回结果。
-
返回最终结果:
return returnList;返回构建好的菜单树列表。
第二段代码是一个递归方法recursionFn
-
getChildList方法:List<SysMenu> childList = getChildList(list, t);通过调用
getChildList方法获取当前节点t的子节点列表。 -
设置子节点列表:
t.setChildren(childList);将获取到的子节点列表设置到当前节点
t的children属性中。 -
递归处理子节点:
for (SysMenu tChild : childList) {if (hasChild(list, tChild)) {recursionFn(list, tChild);}
}
遍历当前节点的子节点列表,对每个子节点进行递归处理。如果子节点还有子节点(通过 hasChild 方法判断),则继续递归调用 recursionFn 方法。
整个递归方法的作用是从当前节点开始,递归地设置其子节点列表,并对每个子节点的子节点进行递归处理,以构建完整的树形结构。这种递归方式有助于处理树状结构的数据,例如在构建菜单树时,每个菜单节点都包含了其下级菜单的引用。
相关文章:
Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)
Java实现树结构(为前端实现级联菜单或者是下拉菜单接口) 我们常常会遇到这样一个问题,就是前端要实现的样式是一个级联菜单或者是下拉树,如图 这样的数据接口是怎么实现的呢,是什么样子的呢? 我们可以看看 …...
MySQL中常用的数据类型
整型 int 有符号范围: -2147483648 ~ 2147483647 int unsigned 无符号范围: 0 ~ 4294967295 int(5) zerofill 仅用于显示,当不满足5位时,按照左边补0,例如: 00002满足时,正常显示 tinyint[(m)] [unsigned] [zerofill] 有符号&a…...
HTML+CSS+JS制作三款雪花酷炫特效
🎀效果展示 🎀代码展示 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html...
[C#]使用ONNXRuntime部署一种用于边缘检测的轻量级密集卷积神经网络LDC
源码地址: github.com/xavysp/LDC LDC: Lightweight Dense CNN for Edge Detection算法介绍: 由于深度学习方法的快速发展,近年来,用于执行图像边缘检测的卷积神经网络(CNN)模型爆炸性地传播。但边缘检测…...
ZigBee案例笔记 - 无线点灯
文章目录 无线点灯实验概述工程关键字工程文件夹介绍Basic RF软件设计框图简单说明工程操作Basic RF启动流程Basic RF发送流程Basic RF接收流程 无线点灯案例无线点灯现象 无线点灯实验概述 ZigBee无线点灯实验(即Basic RF工程),由TI公司提供…...
Debezium日常分享系列之:向 Debezium 连接器发送信号
Debezium日常分享系列之:向 Debezium 连接器发送信号 一、概述二、激活源信号通道三、信令数据集合的结构四、创建信令数据集合五、激活kafka信号通道六、数据格式七、激活JMX信号通道八、自定义信令通道九、Debezium 核心模块依赖项十、部署自定义信令通道十一、信…...
《C#程序设计教程》总复习
一、单项选择题 1.short 类型的变量在内存中占据的位数是 ( )。 A. 8 B. 16 C. 32 D. 64 2.对千 int[ 4,5]型的数组 a, 数组元素 a[2,3] 存在数组第 ( )个位置上。 A. 11 B. 12 C. 14 D. 15 3.设 int 类型变量 x,y,z 的值分别是2、3、6 , 那么…...
为什么ChatGPT选择了SSE,而不是WebSocket?
我在探索ChatGPT的使用过程中,发现了一个有趣的现象:ChatGPT在实现流式返回的时候,选择了SSE(Server-Sent Events),而非WebSocket。 那么问题来了:为什么ChatGPT选择了SSE,而不是We…...
appium入门基础
介绍 appium支持在不同平台的UI自动化,如web,移动端,桌面端等。还支持使用java,python,js等语言编写自动化代码。主要用于自动化测试脚本,省去重复的手动操作。 Appium官网 安装 首先必须环境有Node.js用于安装Appium。 总体来…...
jsp介绍
JSP 一种编写动态网页的语言,可以嵌入java代码和html代码,其底层本质上为servlet,html部分为输出流,编译为java文件 例如 源jsp文件 <% page contentType"text/html; charsetutf-8" language"java" pageEncoding&…...
Debian安装k8s记录
Debian安装k8s记录 在master和node上安装kube安装master安装node遇到的问题汇总1、kubelet.service报错 failed to pull image "registry.k8s.io/pause:3.6"2、node重启后报错,failed: open /run/flannel/subnet.env: no such file or directory 在master…...
第6课 用window API捕获麦克风数据并加入队列备用
今天是2024年1月1日,新年的第一缕阳光已经普照大地,祝愿看到这篇文章的所有程序员或程序爱好者都能在新的一年里持之以恒,事业有成。 今天也是我加入CSDN的第4100天,但回过头看一看,这么长的时间也没有在CSDN写下几篇…...
图片预览 element-plus 带页码
vue3、element-plus项目中,点击预览图片,并显示页码效果如图 安装 | Element Plus <div class"image__preview"><el-imagestyle"width: 100px; height: 100px":src"imgListArr[0]":zoom-rate"1.2":max…...
【小白专用】winform启动界面+登录窗口 更新2024.1.1
需求场景:先展示启动界面,然后打开登录界面,如果登录成功就跳转到主界面 首先在程序的入口路径加载启动界面,使用ShowDialog显示界面, 然后在启动界面中添加定时器,来实现显示一段时间的效果,等…...
自动化网络故障修复管理
什么是故障管理 故障管理是网络管理的组成部分,涉及检测、隔离和解决问题。如果实施得当,网络故障管理可以使连接、应用程序和服务保持在最佳水平,提供容错能力并最大限度地减少停机时间。专门为此目的设计的平台或工具称为故障管理系统。 …...
Git:常用命令(二)
查看提交历史 1 git log 撤消操作 任何时候,你都有可能需要撤消刚才所做的某些操作。接下来,我们会介绍一些基本的撤消操作相关的命令。请注意,有些操作并不总是可以撤消的,所以请务必谨慎小心,一旦失误,…...
Oracle 12c rac 搭建 dg
环境 rac 环境 (主)byoradbrac 系统版本:Red Hat Enterprise Linux Server release 6.5 软件版本:Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit byoradb1:172.17.38.44 byoradb2:…...
Cisco模拟器-交换机端口的隔离
设计要求将某台交换机的端口划分在不同的VLAN。以实现连接在相同VLAN端口上的计算机可以通信,而连接在不同VLAN端口上的计算机无法通信的目的。 通过设计,一方面可以加强计算机网络的安全,另一方面通过隔绝不同VLAN间的广播包也可以提高网络…...
zdppy_api框架快速入门
概述 zdppy_api是一款为了快速开发而生的,基于异步的,使用简单的Python后端API接口开发框架。 本框架的目标是让Python后端开发变得越来越简单,直到发现原来还可以更简单! 一切都是为了提高开发效率!!&…...
https证书配置过程
相关网址: FreeSSL首页 - FreeSSL.cn一个提供免费HTTPS证书申请的网站 ACME v2证书自动化快速入门 acme.sh简单教程-CSDN博客...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...
