菜单功能列表嵌套菜单解决方案

1、通过mapper定义sql的自循环,在查询时就完成菜单的嵌套

数据库字段分析

首先我们来看看数据库中的表字段
在这里插入图片描述

显然parentId就是用来做嵌套的切入点

完善表字段

在IDE中我们需要为Menu这个实体类定义个Lis列表数组,用来装载菜单嵌套的数据

注意:我们使用的持久层框架是MybatisPlus,所以添加注解告诉mybatisplus,这个List字段在数据库不存在

在这里插入图片描述

接下来我们在mapper.xml文件中定义sql语句,这里建议在Navicat或者sqlyog中编写sql语句,并模拟数据执行测试,这是为了假如后期报错能更容易的定位。如果sql之前能运行成功,那么出错一定在我们后端逻辑

分析sql逻辑

在这里插入图片描述

定义实体类映射
<!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>
<!-- 这里继承BaseResultMap,避免重复定义映射字段 -->
<resultMap id="Menus" type="com.shuang.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <!--根据用户id查询用户菜单-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT m1.*,
                        m2.id          AS id2,
                        m2.url         AS url2,
                        m2.path        AS path2,
                        m2.component   AS component2,
                        m2.`name`      AS name2,
                        m2.iconCls     AS iconCls2,
                        m2.keepAlive   AS keepAlive2,
                        m2.requireAuth AS requireAuth2,
                        m2.parentId    AS parentId2,
                        m2.enabled     AS enabled2
        FROM yeb.t_menu m1,
             yeb.t_menu m2,
             yeb.t_admin_role ar,
             yeb.t_menu_role mr
        WHERE m1.id = m2.parentId
          AND m2.id = mr.mid
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          and m2.enabled = true
    </select>
执行测试

成功完成父子菜单的数据嵌套

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0wOIwZX-1623554505660)(云e办项目总计.assets/image-20210613105849868.png)]

2、在后端逻辑中完成菜单关系的嵌套

在service层,根据用户id查询用户所拥有的的菜单

@Override
public List<SysMenuDto> getCurrentUserNav() {

    String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    SysUser sysUser = sysUserService.getByUsername(username);

    // 根据用户id查询用户所拥有的的菜单
    List<Long> navMenuIds = sysUserMapper.getNavMenuIds(sysUser.getId());
    List<SysMenu> sysMenus = this.listByIds(navMenuIds);

    // 转树状结构
    List<SysMenu> menuTree = buildTreeMenu(sysMenus);

    // 实体转DTO
    return convert(menuTree);
}

上面这段代码涉及了好几个方法,接下来我们逐个来解释

(String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 这里获取用户名是由于我们项目里集成了springsecurity后完成的自动登录,便可以直接获取用户登录后的信息

sysUserService.getByUsername(username)是我们在serviceImpl实现类中定义的方法,用的mybatisplus的内置方法

@Override
    public SysUser getByUsername(String username) {

        return getOne(new QueryWrapper<SysUser>().eq("username", username));
    }

sysUserMapper.getNavMenuIds(sysUser.getId());这是在mapper中定义的根据用户id查询菜单的信息的方法,这段代码使用到了多表连接,与上面类似这里不再详述

<select id="getNavMenuIds" resultType="java.lang.Long">
        SELECT Distinct rm.menu_id
        FROM vueadmin.sys_user_role ur
                 LEFT JOIN vueadmin.sys_role_menu rm ON ur.role_id = rm.role_id
        where ur.user_id = #{userId}
    </select>

this.listByIds(navMenuIds);this指的是本类,从下面这个图片可以看到通过mybatisplus的自动代码生成器,帮我们在service层完成对mapper的继承使用,但是如果涉及到复杂的sql语句的编写, 还是建议使用mapper

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9MS8Chj-1623554505661)(云e办项目总计.assets/image-20210613111018677.png)]

List<SysMenu> menuTree = buildTreeMenu(sysMenus);这里对从数据库查询出来的菜单列表,转换成树状的形式,也就是完成菜单关系的嵌套

private List<SysMenu> buildTreeMenu(List<SysMenu> menus) {

        List<SysMenu> finalMenus = new ArrayList<>();

        // 给当前的menu的所有子类都找到
        // 先各自寻找的各自的孩子
        for (SysMenu menu : menus){

            for (SysMenu child : menus){
                if (menu.getId().equals(child.getParentId())){
                    menu.getChildren().add(child);
                }
            }
            if (menu.getParentId()==0L){
                finalMenus.add(menu);
            }
        }

        // 提取出父节点
        return finalMenus;
    }

实体转树状方法的逻辑思路:我们先使用foreach(SysMenu menu : menus)循环遍历每一个菜单项,在第一次循环体中在进行一个foreach(SysMenu child : menus),循环对象跟前面的foreach是一样的,因为我们在SysMenu中有个参数parentId,所以在第二次循环的时候通过判断当第一次遍历的对象id跟第二次循环体的parentId是否相同,menu.getId().equals(child.getParentId()),如果相同就说明child是menu的子菜单项,就把child添加进menu,menu.getChildren().add(child);,如果不相同则不做任何处理,经过双重循环遍历就能够获取出菜单树\

onvert(menuTree);对返回结果的数据格式进行转换

我们在查询获取到菜单树状信息后,需要转换为前端想要接受的参数格式,所以我们在前面定义了SysMenuDto,正是要在这里进行转换,在返回转换后的Dto实体类,在转换过程中我们在判断菜单项是否有子菜单,如果有则进行递归转换的方法

3、通过mybatis提供的循环递归去实现多级菜单

mapper.xml文件

注意第17行的代码,column中的id是第一次查询出来children的id,通过children的id作为父id继续去查改children是否还存在children,一开始是用-1去查询parentId,将查询出来的记录的id作为第二次查询的parentId,进行递归查询知道parentId的child为空,递归结束

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shuang.server.mapper.DepartmentMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Department">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="parentId" property="parentId" />
        <result column="depPath" property="depPath" />
        <result column="enabled" property="enabled" />
        <result column="isParent" property="isParent" />
    </resultMap>

    <resultMap id="DepartmentWithChildren" type="com.shuang.server.pojo.Department" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Department"
                    select="com.shuang.server.mapper.DepartmentMapper.getAllDepartments" column="id">
        </collection>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, parentId, depPath, enabled, isParent
    </sql>

    <!--获取所有部门-->
    <select id="getAllDepartments" resultMap="DepartmentWithChildren">
        select
        <include refid="Base_Column_List"/>
        from yeb.t_department
        where parentId = #{parentId}
    </select>

</mapper>

在这里插入图片描述
在这里插入图片描述

4、总结

前一种方法是在定义sql时就已经完成对菜单关系的嵌套,并通过定义实体类映射关系来完成菜单查询

而第二种方法是直接从数据库查询出数据后,在对菜单id与父id的值进行比较,之后通过嵌套循环的方式来完成菜单关系的嵌套

最后一种方法则是通过mybatis的一种递归查询实现的

GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:1 个月前 )
960b763e 3 个月前
8c391e04 6 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐