树形菜单结构分析

本文基于云尚办公项目,角色菜单进行分析,便于自己理解其中思路。

一、数据库建表分析

1.角色菜单表sys_role_menu如下

1.1数据库建表分析

如图所示,role_id对应角色的id,menu_id对应用户的菜单的id

注意区分角色和用户的差别,在这个系统当中,角色可以理解为职称,用户可以理解为每个独立的人,一个人可以拥有多个职称,一个职称也可以被多个人单人,所以是多对多的关系,所以需要第三张表即本表来存储role和menu之间的关系

2.角色表sys_role如下

3.菜单表sys_menu表如下

二、代码实现

此处直接展示代码,不多赘述


// 业务层逻辑
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    @Override
    public List<SysMenu> findNodes() {
        // 查询所有的菜单数据
        List<SysMenu> sysMenuList = baseMapper.selectList(null);
        // 构建树形结构
        List<SysMenu> resultList =  MenuHelper.buildTree(sysMenuList);
        return resultList;
    }
}


// 树形递归查询逻辑
package com.atcui.auth.util;

import com.atguigu.model.system.SysMenu;

import java.util.ArrayList;
import java.util.List;

/**
 * 树形菜单工具类
 */
public class MenuHelper {
    public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
        // 创建一个list集合用于存储最终数据
        List<SysMenu> tree = new ArrayList<>();
        // 把所有的菜单数据进行便利
        for (SysMenu sysMenu : sysMenuList) {
            // 递归的入口进入 parentId=0是入口
            if (sysMenu.getParentId() == 0) {
                tree.add(getChildren(sysMenu,sysMenuList));
            }
        }
        return tree;
    }

    private static SysMenu getChildren(SysMenu sysMenu, List<SysMenu> sysMenuList) {
        sysMenu.setChildren(new ArrayList<SysMenu>());
        // 便利所有的菜单数据 判断id和parentId的关系
        for (SysMenu menu : sysMenuList) {
            if (sysMenu.getId().longValue() == menu.getParentId().longValue()) {
                if (sysMenu.getChildren() == null) sysMenu.setChildren(new ArrayList<>());
                sysMenu.getChildren().add(getChildren(menu,sysMenuList));
            }
        }
        return sysMenu;
    }
}

三、逻辑分析

  1. Service层分析


@Override
    public List<SysMenu> findNodes() {
        // 查询所有的菜单数据
        List<SysMenu> sysMenuList = baseMapper.selectList(null);
        // 构建树形结构
        List<SysMenu> resultList =  MenuHelper.buildTree(sysMenuList);
        return resultList;
    }

不难看出,service中这个方法向Web层的Controller返回了一个树形结构,而这个树形结构,是由MenuHelper类中的静态方法buildTree()方法返回的,需要参数为sysMenuList,所有种类的菜单。

  1. MenuHelper类分析

2.1 buildTree()方法分析

那么重头戏的分析就集中在MenuHelper这个工具类的实现上。


public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
        // 创建一个list集合用于存储最终数据
        List<SysMenu> tree = new ArrayList<>();
        // 把所有的菜单数据进行遍历
        for (SysMenu sysMenu : sysMenuList) {
            // 递归的入口进入 parentId=0是入口
            if (sysMenu.getParentId() == 0) {
                tree.add(getChildren(sysMenu,sysMenuList));
            }
        }
        return tree;
 }

在此,我们首先要明确角色菜单的实现方法是递归,所以,为了返回最终结果,我们需要定义一个List存储最后返回的最终树形结构。

2.2 SysMenu类的补充说明

ps:这边可能会有一个疑问,那么这个List集合中泛型是SysMenu类,那么如何存储的子节点呢?这时候,就要看到这个类的具体代码


    // 下级列表
    @TableField(exist = false)
    private List<SysMenu> children;
    //是否选中
    @TableField(exist = false)
    private boolean isSelect;

在这个实体类中,我们特别设置了两个字段用来存储不存在于数据库中的数据(由注解@TableField(exist = false))体现,那么第一层就全部是大菜单(parent_id = 0),大菜单里面包含了其子菜单.

2.3 递归思想的分析

好,我们继续看下去。

既然是递归实现,对于递归来说最重要的就是入口和出口,出口我们很容易得出,我们对所有菜单即sysMenuList进行遍历时,当遍历结束时,那么一层的递归自然会自动结束

而对于入口,我们需要的是,一个完整的菜单,我们在上面说过,菜单的第一层应该是一级菜单,即parent_id = 0,那么毫无疑问的,我们第一步是获取所有的一级菜单,那么递归的入口很快就得出,如果sysMenu.getParentId() == 0,就开始递归。

2.4 getChildren(sysMenu,sysMenuList)方法

讲到这儿,对于递归的入口和出口,已经有了很清晰的诉说,那么当满足入口条件进入后,我们又该怎么做呢?举个例子,我们进入了一级菜单Father后,我们需要对Father的所有子菜单Sons进行查询,那么每个子菜单Son和父菜单Father之间的关系如何?

子菜单Son的Parent_id 等于父菜单Father的Id


private static SysMenu getChildren(SysMenu sysMenu, List<SysMenu> sysMenuList) {
        sysMenu.setChildren(new ArrayList<SysMenu>());
        // 便利所有的菜单数据 判断id和parentId的关系
        for (SysMenu menu : sysMenuList) {
            if (sysMenu.getId().longValue() == menu.getParentId().longValue()) {
                if (sysMenu.getChildren() == null) sysMenu.setChildren(new ArrayList<>());
                sysMenu.getChildren().add(getChildren(menu,sysMenuList));
            }
        }
        return sysMenu;
}

对于getChildren()方法,接收的参数有两个,第一个是,当前正在被构建子节点的父节点(一级目录)sysMenu,第二个是,所有的菜单sysMenuList。

首先是要对children字段(上面已经描述过该字段,此处不再赘述)进行初始化,进行初始化结束后,对其进行赋值,那么赋值的过程做法,经过上面的分析后就非常简单明了,就是对所有的菜单选项进行遍历,判断 当前循环到的menu的parentId = 其父节点sysMenu的Id

sysMenu.getId().longValue() == menu.getParentId().longValue()

如果相同,那么父节点的childern就需要add,加入当前循环到的menu。

2.5 递归的关键点

而为了一次性构建,这边并不是直接add(menu),如果是这样直接加入,那么并不能体现出树形结构的特点,而是使用


sysMenu.getChildren().add(getChildren(menu,sysMenuList));

这样的一段代码,这样如果当前节点(不管是所谓的父节点还是子节点,在递归的过程中,子节点在下一次的递归中就会成为父节点)还有子节点,那么就会进行递归,当完成好这个节点所有子节点的填充后,递归才会结束,进入for循环,继续进行遍历,开始下一个一级菜单的递归查询。

四、总结

此处代码可以进一步优化,使用stream流来处理,后续进一步完善,同时对于角色菜单的实现需要非常清晰的头脑去理解,由于本人有数据结构基础,同时时间忙碌,此处只是为了总结代码的使用,所以暂未画出递归树,后期可能会补全。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值