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

news2024/11/9 9:36:25

文章目录

  • 一、表结构设计
  • 二、菜单管理接口
    • 2.1 查询菜单
    • 2.2 添加菜单
    • 2.3 修改菜单
    • 2.4 删除菜单
  • 三、分配菜单
    • 3.1 查询菜单
    • 3.2 保存菜单(批量插入)
  • 四、动态菜单
  • 五、解决bug

一、表结构设计

菜单管理就是对系统的首页中的左侧菜单进行维护。

一个用户可以担任多个角色,反之亦然,因此用户表与角色表是多对多的关系;一个角色操作多个菜单,一个菜单可以被多个角色操作,因此角色表与菜单表也是多对多的关系。为了建立用户表与角色表的联系,需要建立角色用户关系表,用来存储uid与roleid;同样,也需要建立角色菜单关系表,通过roleid与mid来建立角色表与菜单表的联系

在这里插入图片描述
此次来完成菜单功能模块,因此来看看角色表具体是如何设计的:

首先是菜单表,表中含有一个重要字段parent_id,当parent_id值为0时,表明该菜单是第一层。

CREATE TABLE `sys_menu` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '所属上级',
  `title` varchar(20) NOT NULL DEFAULT '' COMMENT '菜单标题',
  `component` varchar(100) DEFAULT NULL COMMENT '组件名称',
  `sort_value` int NOT NULL DEFAULT '1' COMMENT '排序',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁止,1:正常)',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:可用 1:不可用)',
  PRIMARY KEY (`id`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表'

最后就是角色菜单表,用role_idmenu_id来建立角色表和菜单表之间的联系,字段is_half表示菜单节点是否是半选中状态。

CREATE TABLE `sys_role_menu` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_id` bigint NOT NULL DEFAULT '0',
  `menu_id` bigint NOT NULL DEFAULT '0',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:可用 1:不可用)',
  `is_half` tinyint DEFAULT NULL, '(0:否 1:是)'
  PRIMARY KEY (`id`),
  KEY `idx_role_id` (`role_id`),
  KEY `idx_menu_id` (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8mb3 COMMENT='角色菜单'

二、菜单管理接口

由于菜单表结构是一种树形结构,因此在进行数据展示的时候需要按照树形表格的方式进行数据展示。 效果图如下:
在这里插入图片描述

2.1 查询菜单

首先来看一下实体类,这里添加了一个children字段,是为了实现下级列表
在这里插入图片描述
让我们来debug一遍,首先来到controller层:
在这里插入图片描述
接着来到业务层,为了实现功能,我首先是查询菜单表,获取所有的菜单数据,可以看到,我查询到了19个菜单数据。
在这里插入图片描述
最后通过MenuHelper.buildTree(sysMenuList)调用,返回正确的数据格式给前端处理
在这里插入图片描述

类MenuHelper具体方法实现如下: buildTree()用来构建菜单,循环遍历所有的菜单数据,当某一个菜单的parent_id值为0时,代表着这个菜单是第一级;然后调用findChildren()来递归找其子节点。同样,findChildren()中判断是其子节点的条件是:循环遍历整个菜单数据,当当前菜单的id值与遍历的菜单的parent_id值相等。

public class MenuHelper {
    /**
     * 使用递归方法建菜单
     * @param sysMenuList
     * @return
     */
    public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
        List<SysMenu> trees = new ArrayList<>();
        for (SysMenu sysMenu : sysMenuList) {
            //if为true即代表是第一级
            if (sysMenu.getParentId() == 0) {
                trees.add(findChildren(sysMenu,sysMenuList));
            }
        }
        return trees;
    }
    /**
     * 递归查找子节点
     * @param treeNodes
     * @return
     */
    private static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> treeNodes) {
        sysMenu.setChildren(new ArrayList<SysMenu>());
        for (SysMenu it : treeNodes) {
            //即当前菜单的id值与所有菜单的parent_id值相等
            if(sysMenu.getId().longValue() == it.getParentId().longValue()) {
                sysMenu.getChildren().add(findChildren(it,treeNodes));
            }
        }
        return sysMenu;
    }
}

SQL语句编写如下:

    @Select("select * from sys_menu where is_deleted = 0 order by sort_value")
    List<SysMenu> selectAll();

2.2 添加菜单

需求说明:
当用户点击添加按钮的时候,弹出对话框,当用户在该表单中点击提交按钮的时候此时就需要将表单进行提交,在后端需要将提交过来的表单数据保存到数据库中即可。页面效果如下所示:
在这里插入图片描述
添加菜单的功能很简单,就是向菜单表中插入一条数据,添加二级节点也是如此;这里就不过多介绍了
在这里插入图片描述
在这里插入图片描述
来看看SQL语句是如何写的:

@Mapper
public interface SysMenuMapper {
    @Insert("insert into sys_menu (id, parent_id, title, component, sort_value, status)\n" +
            "values (#{id},#{parentId},#{title},#{component},#{sortValue},#{status})")
    void save(SysMenu sysMenu);
}

2.3 修改菜单

需求说明:
当用户点击修改按钮的时候,弹出对话框,在该对话框中需要将当前行所对应的菜单数据在该表单页面进行展示。当用户在该表单中点击提交按钮的时候那么此时就需要将表单进行提交,在后端需要提交过来的表单数据修改数据库中的即可。页面效果如下所示:
在这里插入图片描述
这里的业务逻辑跟添加类似,不同的是SQL语句的编写,来看看SQL语句是如何编写的:

    <!--    void updateById(SysMenu sysMenu);-->
    <update id="updateById">
        update sys_menu set
        <if test="parentId != null">parent_id = #{parentId},</if>
        <if test="title != null and title != ''">title = #{title},</if>
        <if test="component != null and component != ''">component = #{component},</if>
        <if test="sortValue != null">sort_value = #{sortValue},</if>
        <if test="status != null">status = #{status},</if>
        update_time = now()
        where
        id = #{id}
    </update>

2.4 删除菜单

需求说明:
当点击删除按钮的时候此时需要弹出一个提示框,询问是否需要删除数据?如果用户点击是,那么此时向后端发送请求传递id参数,后端接收id参数进行逻辑删除。页面效果如下所示:
在这里插入图片描述
让我们debug一下,来到controller层,获取要删除菜单的id值
在这里插入图片描述
接着进入业务层,要先判断要删除的菜单是否存在子菜单,只需要从菜单表中查询,如果parent_id等于要删除菜单的id值,说明有子菜单,不能删除,抛出异常。
在这里插入图片描述
SQL语句编写如下:

    @Select("select count(*) from sys_menu where parent_id = #{id} and is_deleted = 0")
    int countByParentId(Long id);

三、分配菜单

需求说明:
在角色列表页面,当用户点击分配菜单按钮的时候,此时就会弹出一个对话框。在该对话框中会将系统中所涉及到的所有的菜单都展示出来。并且将当前角色所对应的菜单进行选中。效果如下图所示:
在这里插入图片描述

3.1 查询菜单

需求:根据角色的id查询出其对应的菜单id,并且需要将系统中所有的菜单数据查询出来。

让我们debug一遍,首先来到controller层,获取到前端传来的角色id值。
在这里插入图片描述
业务层首先查询所有菜单,这个功能前面已经实现了;接着在角色菜单表中,查询角色分配过的菜单id,放入到list集合中。
在这里插入图片描述
对应的SQL语句如下:

    @Select("select menu_id from sys_role_menu where role_id = #{roleId} and is_deleted = 0 and is_half = 0")
    List<Long> findSysRoleMenuByRoleId(Long roleId);

3.2 保存菜单(批量插入)

思路分析: 前端请求后端接口的时候需要将角色的id和用户所选中的菜单id传递到后端。后端需要先根据角色的id从sys_role_menu表中删除其所对应的菜单数据,然后添加新的菜单数据到sys_role_menu表中。

先来看看请求参数实体类
在这里插入图片描述
debug一遍,来到controller层,请求参数实体类接收到前端传来的参数:roleId值为44,字段menuIdList有6个map数据,分别存菜单id与菜单isHalf。
在这里插入图片描述
进入到业务层,首先是删除角色之前分配过的菜单数据;最后通过批量插入,保存分配菜单的数据。
在这里插入图片描述
业务层的两个SQL分别如下:

    @Delete("delete from sys_role_menu where role_id = #{roleId}")
    void deleteByRoleId(Long roleId);
    <!--void doAssign(Long roleId, List<Map<String, Number>> menuIdList);-->
    <insert id="doAssign">
        insert into sys_role_menu (role_id, menu_id, create_time, update_time, is_deleted, is_half) values
        <foreach collection="menuIdList" item="menuInfo" separator=",">
            (#{roleId} , #{menuInfo.id} , now() , now() , 0 , #{menuInfo.isHalf})
        </foreach>
    </insert>

四、动态菜单

需求说明: 不同用户其所对应的权限是不同的,因此关于左侧菜单需要根据当前登录的用户所对应的角色动态进行获取。

系统菜单响应结果实体类长这样:
在这里插入图片描述
通过debug,来到controller层
在这里插入图片描述
在业务层,首先通过userId查询可以操作的菜单,此时我查询到了6个菜单数据。
在这里插入图片描述

接着通过前面的工具类,将菜单数据封装成树形数据。

在这里插入图片描述

最后调用buildMenus(),将树形数据转化为前端需要的数据格式。
在这里插入图片描述

对应的SQL语句如下,采用的是三表内连接。

    @Select("    SELECT DISTINCT m.* FROM sys_menu m\n" +
            "    INNER JOIN sys_role_menu rm ON rm.menu_id = m.id\n" +
            "    INNER JOIN sys_user_role ur ON ur.role_id = rm.role_id\n" +
            "    WHERE ur.user_id=#{userId} and m.is_deleted = 0")
    List<SysMenu> selectListByUserId(Long userId);

五、解决bug

当我为角色分配菜单,全部选中,比如系统管理全部选中;然后在系统管理下面添加新的子菜单,点击分配角色,新添加的子菜单默认也会选中,这显然是不被允许的。

解决办法:SysMenuServiceImpl更改添加菜单方法,代码如下:

    //添加菜单
    @Override
    public void save(SysMenu sysMenu) {
        sysMenuMapper.save(sysMenu);
        updateSysRoleMenuIsHalf(sysMenu);
    }
    //新添加子菜单,把父菜单isHalf设置为半开状态1
    private void updateSysRoleMenuIsHalf(SysMenu sysMenu) {
        //获取当前添加菜单的父菜单
        SysMenu parentMenu = sysMenuMapper.selectById(sysMenu.getParentId());
        if (parentMenu != null) {
            //将父菜单isHalf设置为1
            sysRoleMenuMapper.updateSysRoleMenuIsHalf(parentMenu.getId());
            //递归调用
            updateSysRoleMenuIsHalf(parentMenu);
        }
    }

对应的两个SQL语句分别如下:

	@Select("select * from sys_menu where id = #{parentId} ")
    SysMenu selectById(Long parentId);
    @Update("update sys_role_menu srm set srm.is_half = 1 where menu_id = #{menuId}")
    void updateSysRoleMenuIsHalf(Long id);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1261517.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解密人工智能:线性回归

导言 人工智能&#xff08;AI&#xff09;已经成为当今科技领域的热门话题&#xff0c;其应用领域涵盖了各个行业。线性回归作为人工智能中的一种关键统计学方法&#xff0c;被广泛应用于预测和决策支持系统中。本文将为您详细介绍线性回归在人工智能中的应用原理与方法&#x…

将用户的session改为分布式共享session

将用户的session改为分布式session 分布式session理解 使用分布式session的原因&#xff1a; 后台服务器是分布式的&#xff08;比如要负载均衡&#xff09;&#xff0c;在A服务器请求的的信息&#xff08;如用户登录信息&#xff09;存在A的session中&#xff0c;B服务器并不…

代码随想录算法训练营 ---第四十八天

第一题&#xff1a; 简介&#xff1a; 注&#xff1a;本题简介是我的思路&#xff0c;题解思路看下方。 动态规划五部曲&#xff1a; 1.确定dp数组的含义 //dp[i]表示 偷到第i家能偷到的最大金额 for(int i2;i<nums.size();i){if(i-3>0)dp[i] max(dp[i-2],dp[i-3])nu…

vue中的keep-alive详解与应用场景

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-keep-alive 目录 一、Keep-alive 是什么 二、使用场景 三、原理分析 四、案例实现 activa…

NB-IoT BC260Y Open CPU SDK④开发环境搭建

NB-IoT BC260Y Open CPU SDK④开发环境搭建 1、SDK包的介绍2、编程工具3、程序框架1、SDK包的介绍 (1)、SDK包的下载: 链接: (2)、文件目录介绍 文件名描述device启动文件、底层配置文档等doc存放 QuecOpen 项目相关的说明文档osFreeRTOS 相关代码out输出编译 App 和调…

06-学成在线添加课程,包含课程基本信息,营销信息,课程计划信息,师资信息

添加课程 界面原型 第一步: 用户进入课程查询列表,点击添加课程按钮,选择课程类型是直播还是录播,课程类型不同那么授课方式也不同 添加的课程和教学机构是一对一的关系 第二步: 用户选完课程形式后,点击下一步填写课程的基本信息和营销信息(两张表) 用户只要填完课程信息就…

SpringCloud--分布式事务实现

一、分布式事务 首先要明白事务是指数据库中的一组操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部不执行&#xff0c;以保持数据的一致性和完整性。在本地事务中&#xff0c;也就是传统的单机事务&#xff0c;必须要满足原子性(Atomicity)、一致性(Consistenc…

错误:FinalShell连接CentOs连接失败

需要说明的是:这个错误不是首次连接发生的,而是多次使用后可能发生的错误 正文: 可能的原因是虚拟机的ip地址发生了变更,原因有以下几点: 最最可能的原因:1.DHCP分配变更&#xff1a; 如果虚拟机使用DHCP来获取IP地址&#xff0c;那么DHCP服务器可能会分配给虚拟机一个新的I…

java设计模式学习之【单例模式】

文章目录 引言单例模式简介定义与用途实现方式&#xff1a;饿汉式懒汉式 UML 使用场景优势与劣势单例模式在spring中的应用饿汉式实现懒汉式实现数据库连接示例代码地址 引言 单例模式是一种常用的设计模式&#xff0c;用于确保在一个程序中一个类只有一个实例&#xff0c;并且…

不小心删除了短信,如何在 Android 上恢复已删除的短信

不小心删除了文字消息在 Android 手机上使用可能会是一种令人痛苦的体验。这些消息可能包含有价值的信息、珍贵的回忆或重要的细节。幸运的是&#xff0c;您可以探索多种方法来恢复这些丢失的消息。在本文中&#xff0c;我们将深入研究可用于检索已删除短信的选项&#xff0c;并…

vue3中readonly和shallowReadonly

readonly: 深度只读数据 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。 只读代理是深层的&#xff1a;访问的任何嵌套 property 也是只读的。 shallowReadonly 浅只读数据 创建一个代理&#xff0c;使其自身的 property 为只读&#xff0c;但不执行…

WhatsApp API号解封教程(内含图片指引和申诉模板)

WhatsApp API 是专门为中大型企业设置的WhatsApp APP页面&#xff0c;API号并不像WhatsApp个人号和企业号一样可以直接从App Store 或Google Play 下载&#xff0c;而是需要对接官方来连接API。 虽然申请WhatsApp API号的程序和手续比较复杂&#xff0c;但是这个操作对于企业来…

算法通关村第二关—手写链表反转(青铜)

链表反转的三种方式 一、建立虚拟头结点辅助反转 为了方便反转&#xff0c;可以创建一个ans结点&#xff0c;让ans.next head,然后后面的结点一次插入到ans.next 在下图中&#xff0c;对&#xff08;1->2->3->4->5&#xff09;进行反转&#xff0c;可以创建ans&…

easyexcel指定sheet页动态给行列加背景色

easyexcel&#xff0c;有多个sheet页&#xff0c;某些sheet页的行、列动态需要加背景色 import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.m…

Spring Cloud,注册中心,配置中心,原理详解

文章目录 Spring Cloud&#xff0c;注册中心&#xff0c;配置中心&#xff0c;原理详解谈谈我个人对 spring Cloud 的理解 注册中心Eureka&#xff1a;服务搭建小结 Ribbo - 负载均衡1. 负载均衡流程2. 负载均衡策略 nacos注册中心1. 配置集群1. 创建 namespace2. 配置命名空间…

MATLAB实战 | 不同形式的三维曲面图

通常&#xff0c;MATLAB中绘制三维曲面图&#xff0c;先要生成网格数据&#xff0c;再调用mesh函数和surf函数绘制三维曲面。若曲面用含两个自变量的参数方程定义&#xff0c;则还可以调用fmesh函数和fsurf函数绘图。若曲面用隐函数定义&#xff0c;则可以调用fimplicit3函数绘…

RuoYi若依前后端分离框架二次开发基础项目

一键改标题&#xff1a;点击ruoyi-ui(ctrlshiftr)改完重启项目 连接本地数据库 全智能生成开发 新建maven项目 菜单创建---栏目创建 无权限删除时&#xff1a;去掉要删除的菜单权限 子菜单&#xff1a;新增 代码生成器cv进去 后端&#xff1a; 前端&#xff1a; 完成&#x…

人工智能与供应链行业融合:预测算法的通用化与实战化

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 让我们一起深入探索人工智能与供应链的融合&#xff0c;以及预测算法在实际应用中的价值&#xff01;&#x1f50d;&#x1f680; 文章目录 前言供应链预测算法的基本流程统计学习模型与机…

顺子日期(14)

顺着日期 public class Main {public static void main(String[] args) {int res 0;//2022年int[] days new int[] {31,28,31,30,31,30,31,31,30,31,30,31};//31,28,31,30,31,30,31,31,30,31,30,31//一三五七八十腊//构造2022年每一天的日期yyyymmddStringBuffer date new…

现在的教师工资这么低了?

最近刷v站看到个帖子t/991351 &#xff0c;程序员转行回乡当老师月薪才2000&#xff0c;现在的教师工资这么低了&#xff1f;月薪3000都不到。 搜索了下&#xff0c;现在有些地方开始#教师全员竞聘上岗# https://weibo.com/1784473157/NpUd3esCv &#xff0c;曾经的铁饭碗也可能…