【业务功能篇69】Springboot 树形菜单栏功能设计

【一】springboot+mysql+mybatis实现菜单树(超详细) springboot+mysql+mybatis实现菜单树(超详细) 阅读详情

业务场景: 系统的界面,前端设计的时候,一般会给一个菜单栏,顶部横向以及左侧纵向的导航栏菜单,这里后端返回菜单栏的时候,就涉及层级父子项的问题,所以返回数据的时候,我们需要按照树化形式返回菜单栏数据,方便用户进行解析读取 

 菜单栏的数据表设计

CREATE TABLE `dwr_quality_management_menu_bar` (
  `uid` int NOT NULL AUTO_INCREMENT,
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '树节点id',
  `parent_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父id,树化时使用',
  `resource_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '唯一资源id',
  `name_cn` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '中文名',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '路径',
  `level` tinyint DEFAULT NULL COMMENT '菜单层级',
  `show_order` int DEFAULT NULL COMMENT '显示顺序',
  `description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '描述',
  `is_home_page` tinyint(1) DEFAULT '2' COMMENT '首页标识:1:是,2:否',
  `update_date` datetime DEFAULT NULL COMMENT '更改时间',
  `picture_id` varchar(255) DEFAULT NULL,
  `picture_url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3845 DEFAULT CHARSET=utf8 COMMENT='菜单栏配置';

控制层

@RestController
@RequestMapping(value = "/management/menu", produces = {"application/json;charset=UTF-8"})
@Validated
public class ManagementMenuController {
    @Autowired(required=false) 
    private ManagementMenuDelegate delegate;  

    @RequestMapping(
    		value = "/getAllMenu", 
    		produces = { "application/json" }, 
    		method = RequestMethod.GET)
    public ResponseVo getAllMenu( @RequestParam(value = "p", required = false) String p) 
            {
    	    
		return delegate.getAllMenu(p);
    }

}

 

服务层 接口

public interface ManagementMenuDelegate {
    ResponseVo getAllMenu(String pdu) ;
    	
}

 

服务层 接口实现 

@Service
public class ManagementMenuDelegateImpl implements ManagementMenuDelegate {

    @Resource
    private MenuBarService menuBarService;

    /**
     * getAllMenu
     * 
     * @param p 
     * @return ResponseVo
     */
    @Override
    public ResponseVo getAllMenu(String p) {
        return ResponseUtils.successResponse(menuBarService.getCompleteMenu(p), "");
    }

}

 

服务层 具体接口

public interface MenuBarService {

    /**
     * 获取所有菜单
     *
     * @param p
     * @return List<ManagementMenuBarVo>
     */
    List<ManagementMenuBarVo> getCompleteMenu(String p);

}

服务层 具体接口实现

@Service
@Slf4j
public class MenuBarServiceImpl implements MenuBarService {

    @Resource
    private ManagementMenuBarMapper menuBarMapper;

    /**
     * getCompleteMenu
     * 
     * @param p
     * @return List<ManagementMenuBarVo>
     */
    @Override
    public List<ManagementMenuBarVo> getCompleteMenu(String p) {
        List<ManagementMenuBarVo> result = TreeUtils.treeing(getAllFlattingMenu());
        if (ObjectUtils.isNotEmpty(p)) {
            for (ManagementMenuBarVo barVo : result) {
                if (barVo.getNameCn().equals(p)) {
                    return barVo.getChildren();
                }
            }
            return new ArrayList<>();
        }
        return result;
    }


        /**
     * getAllFlattingMenu
     * 
     * @return List<ManagementMenuBarVo>
     */
    private List<ManagementMenuBarVo> getAllFlattingMenu() {
        QueryWrapper<ManagementMenuBar> queryWrapper = new QueryWrapper<>();
        // 排序规则与getBarRootNode()一致
        queryWrapper.lambda()
            .orderBy(true, true, ManagementMenuBar::getLevel, ManagementMenuBar::getParentId,
                ManagementMenuBar::getShowOrder);
        List<ManagementMenuBar> menuBarList = menuBarMapper.selectList(queryWrapper);

        return convertToVo(menuBarList);
    }

        /**
     * convertToVo
     * 
     * @param list list
     * @return List<ManagementMenuBarVo>
     */
    private List<ManagementMenuBarVo> convertToVo(List<ManagementMenuBar> list) {
        List<ManagementMenuBarVo> voList = new LinkedList<>();
        for (ManagementMenuBar menuBar : list) {
            ManagementMenuBarVo vo = new ManagementMenuBarVo();
            MyBeanUtils.shallowCopy(menuBar, vo);
            vo.initSuperNodeId();
            vo.initSuperParentNodeId();
            voList.add(vo);
        }
        return voList;
    }

}

dao层 mapper

@Mapper
public interface ManagementMenuBarMapper extends BaseMapper<ManagementMenuBar> {


}

实体类DO表


@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@NoArgsConstructor
@TableName("dwr_quality_management_menu_bar")
public class ManagementMenuBar implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "uid" ,type = IdType.AUTO)
    private String uid;

    /**
     * 树节点id
     */
    @TableField("id")
    private String id;

    /**
     * 父id,树化时使用
     */
    @TableField("parent_id")
    private String parentId;

    /**
     * 唯一资源id
     */
    @TableField("resource_id")
    private String resourceId;

    /**
     * 图片ID
     */
    @TableField("picture_id")
    private String pictureId;

    /**
     * 中文名
     */
    @TableField("name_cn")
    private String nameCn;

    /**
     * 路径
     */
    @TableField("url")
    private String url;

    /**
     * 图片访问路径url
     */
    @TableField("picture_url")
    private String pictureUrl;

    /**
     * 菜单层级
     */
    @TableField("level")
    private Integer level;

    /**
     * 显示顺序
     */
    @TableField("show_order")
    private Integer showOrder;

    /**
     * 描述
     */
    @TableField("description")
    private String description;

    /**
     * 首页标识:1:是,2:否
     */
    @TableField("is_home_page")
    private Integer isHomePage;



    /**
     * 更改时间
     */
    @TableField("update_date")
    private Date updateDate;


}

 

传输前端类VO

  • 作为传输转换类,其中就需要继承我们自定义的一个树化类结构 

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ManagementMenuBarVo extends TreeNode<ManagementMenuBarVo> implements Serializable {

    private static final long serialVersionUID = 1L;

    private String uid;
    private String id;

    private String parentId;

    private String resourceId;

    private String pictureId;

    private String pictureUrl;

    private String nameCn;

    private String url;

    private Integer level;

    private String description;

    private Integer isHomePage;

    private Integer isAccessible;

    private String functionName;

    private Integer showOrder;

    /**
     *
     */

    /**
     * initSuperNodeId
     * 
     */
    @Override
    public void initSuperNodeId() {
        super.setNodeId(this.id);
    }

    /**
     * 初始化TreeNode的parentNodeId属性
     */
    @Override
    public void initSuperParentNodeId() {
        super.setParentNodeId(this.parentId);
    }

}

 

树化工具节点类


/**
 * 树化工具实体
 *
 */
@Data
public abstract class TreeNode<T> {

    // 仅用于树化,序列化时忽略
    @JsonIgnore
    private String nodeId;

    @JsonIgnore
    private String parentNodeId;

    @JsonIgnore
    private T parent;

    private List<T> children = new ArrayList<>();

    /**
     * 初始化TreeNode的NodeId属性
     */
    public abstract void initSuperNodeId();

    /**
     * 初始化TreeNode的parentNodeId属性
     */
    public abstract void initSuperParentNodeId();

}

树化操作工具类 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

package com.xxx.utils;


/**
 * 树化工具
 *
 */
public class TreeUtils {

    /**
     * 树化
     * @param treeNodes 扁平节点数据
     * @return 树化的节点列表(列表中每一节点都是一棵树)
     */
    public static <T extends TreeNode<T>> List<T> treeing(List<T> treeNodes) {
        Map<String, T> nodeMap = new HashMap<>(treeNodes.size());
        treeNodes.forEach(t -> nodeMap.put(t.getNodeId(), t));

        List<T> result = new ArrayList<>();
        for (T treeNode : treeNodes) {
            T parent = nodeMap.get(treeNode.getParentNodeId());
            if (parent != null) {
                parent.getChildren().add(treeNode);
                treeNode.setParent(parent);
                continue;
            }
            // 仅添加顶层节点
            result.add(treeNode);
        }

        return result;
    }

    /**
     * 扁平化
     * @param treeNodes 树化数据
     * @return 扁平数据
     */
    public static <T extends TreeNode<T>> List<T> flattening(List<T> treeNodes) {
        if (!CollectionUtils.isEmpty(treeNodes)) {
            return new ArrayList<>();
        }
        List<T> newList = new LinkedList<>();
        Queue<T> queue = new LinkedList<>(treeNodes);
        while (!queue.isEmpty()) {
            T pollMenuBar = queue.poll();
            List<T> children = pollMenuBar.getChildren();
            if (!CollectionUtils.isEmpty(children)) {
                queue.addAll(children);
            }
            newList.add(pollMenuBar);
        }
        return newList;
    }
}

  •  返回前端的JSON数据格式如下
  • 每个菜单栏层级就比较清晰,通过children集合嵌套当前菜单栏的子菜单栏

 

别再手写递归了!用Hutool的TreeUtil搞定SpringBoot+Vue项目里的树形菜单(附排序踩坑实录) 本文详细介绍了如何使用Hutool的TreeUtil工具在SpringBoot+Vue项目中高效处理树形菜单数据,避免传统递归的复杂性和性能问题。通过实战案例和排序陷阱解析,帮助开发者快速掌握树形结构的构建、排序优化及权限控制技巧,提升开发效率。 阅读详情

相关推荐

Spring Boot 2.x 后台菜单管理:3种树形结构数据查询方案与 MyBatis 实现对比

在后台管理系统的开发中,菜单管理模块的设计与实现往往是系统架构的核心难点之一。本文将深入探讨三种主流的树形菜单查询方案:嵌套查询、左外连接查询和递归查询,从性能、代码复杂度及适用场景三个维度进行全面对比,帮助开发者做出更合理的技术选型。在实际项目中,我们曾遇到一个性能问题:当菜单数量达到3000+时,左外连接查询的响应时间超过了2秒。虽然左外连接减少了数据库查询次数,但返回的结果集会包含重复的父节点信息。嵌套查询是最直观的实现方式,通过在MyBatis中使用子查询获取每个菜单的父菜单信息。

weixin_31556767的博客 278

SpringBoot 构建树形菜单 (Java Stream流 返回树形菜单 )

Java Stream流 返回树形菜单

Justin_ly的博客 872

AHB总线协议

1. 简介         AHB总线规范是AMBA总线规范的一部分,AMBA总线规范是ARM公司提出的总线规范,被大多数SoC设计采用,它规定了AHB (Advanced High-performance Bus)、ASB (Advanced System Bus)、APB (Advanced Peripheral Bus)。AHB用于高性能、高时钟频率的系统结构,典型的应用如ARM核与系统内

ivy_reny的专栏 8万+

springboot 实现tree菜单结构(通过递归的方法)

springboot 实现tree菜单结构(通过递归的方法)

weixin_45172119的博客 1622

别再死记硬背了!用LTspice和Simulink仿真,5分钟搞懂巴特沃斯、切比雪夫、贝塞尔滤波器的区别

本文通过LTspice和Simulink仿真工具,直观对比了巴特沃斯、切比雪夫和贝塞尔滤波器的特性差异。从通带波纹、过渡带陡峭度到相位线性度,详细解析了三种经典滤波器的设计哲学与应用场景,帮助工程师和学生快速掌握滤波器选型技巧,提升电路设计效率。

weixin_33910191的博客 523

分布式 Agent 系统设计:高并发场景下的挑战与解决方案

分布式 Agent 系统由多个分布在不同节点上的智能 Agent 组成,这些 Agent 具有自主决策能力,能够通过通信协作完成复杂任务。每个 Agent 可以看作是一个独立的实体,具备感知环境、处理信息、执行动作的功能,它们相互协作,共同实现系统的整体目标。分布式 Agent 系统在高并发场景下面临着资源调度、通信延迟、状态一致性、容错处理和性能瓶颈等诸多挑战。通过采用动态资源分配、高效通信协议、分布式共识算法、冗余备份等解决方案,可以有效应对这些挑战,提高系统的性能、可靠性和可用性。

shuizhudan223的博客 1292

Eclipse安装yml插件

Eclipse安装yml插件 在使用Eclipse开发SpringBoot项目的时候配置文件有时候会使用yml格式的树形格式的配置文件,Eclipse如果没有安装yml插件则默认不认识yml格式的文件,编辑起来也很容易出错,我们来装下插件让编辑更多容易。 方法/步骤 默认情况下yml格式的文件只是普通的文本格式 点击菜单栏【Help】--》【Eclipse Marketplace...】打开应用市场。 在应用市场搜索【yaml】并点击【install】按钮安装插件。

qq_40015409的博客 2820

深度强化学习研究笔记(1)——入门(马尔科夫决策过程,贝尔曼方程,价值迭代和策略迭代)

1. 强化学习(Reinforcement learning)概述 强化学习(Reinforcement learning)的基本内涵是将问题用代理和环境进行建模。其中代理能够对环境执行一些特定的动作a∈Aa \in Aa∈A,从而到达某个状态s∈Ss \in Ss∈S,然后我们就可以根据该状态为代理赋予特定的奖励rrr。强化学习的基本思想如下图所示: 代理的目标是最大化未来的奖励总和,它通过将...

越野者的博客 5280

SpringBoot开发-树形结构

在管理系统开发中,我们都会涉及到菜单页面展示功能,如下图开源项目若依页面。本篇文章就是记录如何构造树形结构功能无论是若依框架的树形结构构造,还是使用Hutool工具包,首先是获取所有菜单列表,然后将List列表转换成树形结构,然后构造成TreeSelect类型。

qq_42211536的博客 1360

避坑指南:ZYNQ MPSOC QSPI固化时MT25QU256闪存的特殊配置

本文针对Xilinx ZYNQ UltraScale+ MPSoC平台,详细解析了使用Vitis工具将BOOT.bin固化至Micron MT25QU256ABA QSPI NOR Flash时的特殊配置与避坑指南。重点阐述了Init文件的真实作用、电压与频率的精确匹配策略,以及针对该闪存型号的初始化序列,帮助工程师解决烧录失败、启动异常等常见问题,确保系统可靠启动。

weixin_29175469的博客 271

MATLAB算法实战应用案例精讲-【数模应用】校准曲线(附MATLAB、python和R语言代码实现)

模型校准曲线(Calibration Curve),也称为可靠性曲线(Reliability Curve)或概率校准曲线(Probability Calibration Curve),是一种评估分类模型输出概率准确性的图形工具。它可以帮助我们理解模型的预测概率是否与实际标签的分布一致。校准曲线通常包括以下步骤:计算模型预测概率:对于测试集中的每个样本,模型会输出一个概率值,表示样本属于正类的概率。将数据分桶:将这些概率值分成若干个等宽的桶(例如10个桶),每个桶中的样本具有相似的预测概率。

qq_36130719的博客 559

菜单树的实现(SpringBoot + Vue)超详细!

菜单树的实现,基于spring boot + Vue 实现,菜单树类型的需求可以参考这篇

Java程序员十六的博客 1472

华为OD机试 - 整型数组按照个位数排序(Java 双机位C卷 100分)

华为OD机试 双机位C卷题库疯狂收录中,刷题。

学Java,找哪吒 350

Springboot 生成后台排序菜单树

springboot 生成排序菜单树

qq_44001404的博客 796

【2026最新版】Open3D(C++)点云处理算法汇总(C++长期更新版)

C++版本Open3D进行点云处理的一些算法使用案例。博客长期更新,最近一次更新时间为:2026年4月11日。

点云侠的博客 1万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Java中树形菜单分析和实现

背景概述 最近在搭建基于Spring Boot(2.0.2)种子项目框架时,遇到了树形菜单加载问题。特此把解决的问题方案记录下去,供其他小伙伴参考和日后回顾。 方案分析 方案选择 一次性加载完,返回前台需要的数据结构 点击加载,默认记载根层级的菜单。后续点击那一级菜单加载其下的子节点 方案实现 在这里只分析一次性加载实现。第二种实现起来更简单。在这里略过。有兴趣可以自己研究一下...

瘦子没有夏天 2万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

SpringBoot mybatis 实现多级树形菜单

一、前言 iview-admin中提供了 v-org-tree 这么一个vue组件可以实现树形菜单,下面小编来提供一下在element-ui中的使用教程(项目见:https://github.com/lison16/v-org-tree) 小编集成了el-dropdown下拉菜单(鼠标左击显示菜单),和右击自定义菜单,两种方式,效果图如下: 二、使用教程 (1)安装依赖 npm install clipboard npm install v-click-ou...

lchmyhua88的博客 2505

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

关于树形结构查询的接口设计

在实际开发中,经常会遇到树形结构的查询,如:菜单树、公司组织机构树、地区区划树等等业务,这里写下两个接口设计方案已供日后使用。 1.表结构设计 //一般单表树形结构基本上都为以下结构: id,name,parentId,其他业务指标 //多表树形同理,设计思路都是通过parentId关联在一起 2.树形返回类设计 @Data public class TreeNode implements Serializable { private String id; //名称 private S

m0_49605579的博客 1664

spring项目菜单文件树数据库设计与后端接口实现

spring项目菜单文件树数据库设计与后端接口实现

weixin_43127328的博客 573

《尚品甄选》:权限管理之菜单管理,递归实现树形结构查询(debug一遍)

用递归实现树形结构查询。

知识记录 1944

手把手教你搞定菜单权限设计,精确到按钮级别

手把手带你一套搞定菜单权限设计,精确到按钮级别

程序员志哥的博客 4130

springboot动态生成树形结构

框架采用springboot,所有数据都是通过查询数据库获取,数据库增删改后树表也会随之改变,采用递归实现

m0_59505149的博客 2517

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

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

weixin_54542328的博客 5926

springboot多级树形查询-递归

springboot多级树形查询 -- 递归

weixin_45762586的博客 1508

查询接口 - 树形菜单查询接口实现

代码】查询接口 - 树形菜单查询接口实现。

demo_yo的博客 705

SpringBoot + Vue3:实现树形权限菜单

SpringBoot实现从数据库查询到树形数据转换的核心逻辑,并通过接口返回树形数据,前端使用Vue3和Ant Design动态渲染树形组件,实现权限菜单的展示与操作。

Cherish_01的博客 1411
上一篇: 【业务功能篇68】电商项目相关核心设计
下一篇: 【业务功能篇70】可重入锁与不可重入锁
studyday1
博客等级 码龄9年 6454粉丝 255原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值