无限级菜单 mysql设计_无限级菜单/权限树该如何设计

本文介绍了如何设计数据库存储无限级菜单,并详细展示了如何在Java中将查询到的菜单数据转换为树形结构,包括转换工具类的实现和在模板引擎中渲染无限级菜单的方法。
摘要由CSDN通过智能技术生成

前言

在开发中我们经常会遇到:导航菜单、部门菜单、权限树、评论等功能。

这些功能都有共同的特点:

有父子关系

可无限递归

我们以导航菜单为例, 我们将导航菜单设置为动态的, 即从动态加载菜单数据。

数据库设计

适用于数据库存储的设计如下:

create table `menus`

(

`id` int primary key auto_increment,

`name` varchar(20) comment '菜单名称',

`pid` int default 0 comment '父级 ID, 最顶级为 0',

`order` int comment '排序, 序号越大, 越靠前'

)

前端渲染

对于前端来说, 我们一般需要这种效果:

菜单配置页面:

1d1d469faf7f

image

对应的导航菜单:

1d1d469faf7f

image

这些插件一般需要这两种格式:

基础格式:

[

{

"id": 1,

"name": "权限管理",

"pid": 0,

"order": 1

},

{

"id": 2,

"name": "用户管理",

"pid": 1,

"order": 2

},

{

"id": 3,

"name": "角色管理",

"pid": 1,

"order": 3

},

{

"id": 4,

"name": "权限管理",

"pid": 1,

"order": 4

}

]

树形格式:

[

{

"id": 1,

"name": "权限管理",

"pid": 0,

"order": 1,

"children": [

{

"id": 2,

"name": "用户管理",

"pid": 1,

"order": 2,

"children": []

},

{

"id": 3,

"name": "角色管理",

"pid": 1,

"order": 3,

"children": []

},

{

"id": 4,

"name": "权限管理",

"pid": 1,

"order": 4,

"children": []

}

]

}

]

有的插件这两种格式都支持, 而有些只支持树形结构, 但我们数据库查询出来的结果往往又是普通结构, 这时候我们就需要将普通格式转换成树形格式。

这个转换一般是在服务端进行(因为前端插件大多都是请求后台的一个 URL 来接收 JSON 数据, 没有提供加载数据后 - 渲染前的事件, 所以无法在前端完成转换.)

数据转换

首先有 Java 实体类:

public class Menu {

private int id,

private String name,

private int pid

// getter setter 略

}

数据库查询后的一般是在 List 中:

List

然后我们需要将这个 List 转换为树形结构, 首先定义一个树形结构的 VO 类:

public class MenuTreeVO {

private int id,

private String name,

private int pid,

private List children,

// getter setter 略

}

转换工具类:

package im.zhaojun.util;

import im.zhaojun.model.vo.MenuTreeVO;

import java.util.ArrayList;

import java.util.List;

public class TreeUtil {

/**

* 所有待用"菜单"

*/

private static List all = null;

/**

* 转换为树形

* @param list 所有节点

* @return 转换后的树结构菜单

*/

public static List toTree(List list) {

// 最初, 所有的 "菜单" 都是待用的

all = new ArrayList<>(list);

// 拿到所有的顶级 "菜单"

List roots = new ArrayList<>();

for (MenuTreeVO menuTreeVO : list) {

if (menuTreeVO.getParentId() == 0) {

roots.add(menuTreeVO);

}

}

// 将所有顶级菜单从 "待用菜单列表" 中删除

all.removeAll(roots);

for (MenuTreeVO menuTreeVO : roots) {

menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));;

}

return roots;

}

/**

* 递归函数

* 递归目的: 拿到子节点

* 递归终止条件: 没有子节点

* @param parent 父节点

* @return 子节点

*/

private static List getCurrentNodeChildren(MenuTreeVO parent) {

// 判断当前节点有没有子节点, 没有则创建一个空长度的 List, 有就使用之前已有的所有子节点.

List childList = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren();

// 从 "待用菜单列表" 中找到当前节点的所有子节点

for (MenuTreeVO child : all) {

if (parent.getMenuId().equals(child.getParentId())) {

childList.add(child);

}

}

// 将当前节点的所有子节点从 "待用菜单列表" 中删除

all.removeAll(childList);

// 所有的子节点再寻找它们自己的子节点

for (MenuTreeVO menuTreeVO : childList) {

menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));

}

return childList;

}

}

调用方式:

// 从数据库获取

List

// Menu 转为 MenuTreeVO

List menuTreeVOS = new ArrayList<>();

for (Menu menu : menus) {

MenuTreeVO menuTreeVO = new MenuTreeVO();

BeanUtils.copyProperties(menu, menuTreeVO);

menuTreeVOS.add(menuTreeVO);

}

// 调用转换方法

xxxUtil.toTree(menuTreeVOS);

// 通过 Json 或 ModelAndView 返回给前台.

附:模板引擎渲染

有时我们会使用模板引擎来渲染菜单, 但由于菜单是树形结构的, 所以在模板引擎中单纯的使用 for 是无法完成无限极菜单的渲染的.

这里有一个很新奇的方法, 我以 thymeleaf 引擎为例:

index.html 的导航部分:

public.html 公共模板部分:

系统管理

基本逻辑就是使用 include 引用模板, 各种模板引擎都有这种功能, 然后判断当前节点有没有子节点, 有的话, 模板文件引用自身, 来完成递归.

结语

上述代码是在开发一个 Shiro 的权限管理后台的时候的一些思路和代码, 完整的代码可以参考: https://github.com/zhaojun1998/Shiro-Action

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值