Java中实现树形菜单的两种方式
作者:我还是个孩子
这篇文中,我一共会用两种方式来实现目录树的数据结构,两种写法逻辑是一样的,只是一种适合新手理解,一种看着简单明了但是对于小白不是很好理解,在这里我会很详细的讲解每一步代码,主要是方便新人看懂,弥补曾经自己学习过程中的苦恼,需要的朋友可以参考下
一、什么是目录结构?
就是在实际开发过程中,总会遇到菜单,或则是权限,这个时候就涉及到后端返回数据给前端的时候,不能一个集合把数据一股脑的全部扔给前端,总要把数据整理好,做成像书目录一样的结构返回给前端。就像以下图示一样
二、目录树结构实现写法
1、准备阶段
①创建数据表
PS:如果是练习可以不用创建数据库,数据全部通过java代码来创建也可以
CREATE TABLE permission_directory ( id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', parent_id int(11) NOT NULL DEFAULT '0' COMMENT '父目录ID', menu_name varchar(255) NOT NULL COMMENT '菜单名称', menu_level int(11) NOT NULL COMMENT '菜单等级', route varchar(255) NOT NULL COMMENT '路由', PRIMARY KEY (id) COMMENT '主键', UNIQUE KEY parent_id (parent_id,menu_name,menu_level,route) COMMENT '唯一索引,包含父目录ID、菜单名称、菜单等级和路由' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '存储引擎为InnoDB,字符集为utf8';
②向表中插入数据
INSERT INTO permission_directory (parent_id, menu_name, menu_level, route) VALUES (1, '首页', 0, '/index'), (2, '系统设置', 0, '/user/manage'), (3, '操作手册', 0, '/role/manage'), (4, '菜单管理', 2, '/menu/manage'), (5, '用户管理', 2, '/system/setting'), (6, '日志管理', 3, '/log/manage'), (7, '定时任务', 3, '/task/schedule'), (8, 'API接口文档', 3, '/api/documentation'), (9, '操作手册', 8, '/operation/manual');
③创建菜单对象PermissionDirectory类
PS:这里我用了@Data注解,就不用封装属性了,如果没写@Data注解就把每个属性封装以下,也就是get()和set()方法
@Data public class PermissionDirectory { @MyAnnotation("主键id") private int id; @MyAnnotation("父目录id") private int parentId; @MyAnnotation("菜单名称") private String menuName; @MyAnnotation("菜单等级") private int menuLevel; @MyAnnotation("路由") private String route; }
④创建存储菜单对象PermissionDirectoryResVO类
@Data public class PermissionDirectoryResVO { @MyAnnotation("主键id") private Integer id; @MyAnnotation("父目录id") private Integer parentId; @MyAnnotation("菜单名称") private String menuName; @MyAnnotation("菜单等级") private Integer menuLevel; @MyAnnotation("路由") private String route; @MyAnnotation("用于存储当前目录下面的全部子集") private List<PermissionDirectoryResVO> authMenuList; }
2、逻辑代码实现
这里关于如何去连接数据库啊等等一系列都省略了,关键就是目录树的逻辑讲解
①第一种写法
public List<PermissionDirectoryResVO> searchMenu() { List<PermissionDirectoryResVO> directoryTree = new ArrayList<>(); List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList(); if (CollectionUtil.isNotEmpty(menuList)){ List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> { PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO(); BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO); return permissionDirectoryResVO; }).collect(Collectors.toList()); pdr.forEach(e ->{ List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr); e.setAuthMenuList(pdrList != null ? pdrList : null); }); List<PermissionDirectoryResVO> parentNodes = pdr.stream(). filter(e -> e.getParentId().equals(0)).collect(Collectors.toList()); directoryTree.addAll(parentNodes); } return directoryTree; } /** * 获取全部子集 * @param id * @param list * @return */ public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){ return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList()); } }
第一种写法代码详细解
第一步:创建存储最终结果数据的集合容器 List<PermissionDirectoryResVO> directoryTree = new ArrayList<>(); 第二步:获取需要整理成树状结构的所有数据 List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList(); PS:这里我是通过查询数据获取的数据,练习的话,可以new一些数据出来存入集合中就行了 第三步:判断获取的数据是否为空,如果为空的话就没有去整理成树结构的必要了,数据都没有 if (CollectionUtil.isNotEmpty(menuList)){ .... } PS:这里我用的是糊涂类提供的方法进行判断,如果小白在写的过程中发现报错,找不到这个方法或则这个类就换一种写法 第四步:将获取的PermissionDirectory数据全部赋值给PermissionDirectoryResVO List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> { PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO(); BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO); return permissionDirectoryResVO; }).collect(Collectors.toList()); 具体解释如下: menuList.stream():将menuList集合转换为一个流(Stream) map(PermissionDirectory -> {...}):这个简单理解就是循环menuList集合,然后遍历集合中的每一个PermissionDirectory元素 BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO):将PermissionDirectory对象的属性值复制到permissionDirectoryResVO对象中。这样,authMenuResVO对象就具有了与AuthMenu对象相同的属性值。 return permissionDirectoryResVO:将转换后的permissionDirectoryResVO对象作为结果返回给调用者。 collect(Collectors.toList()):将处理后的流中的元素收集到一个新的列表中,并返回该列表 因此,这段代码的作用是将原始列表menuList中的每个元素转换为AuthMenuResVO类型的对象,并将转换后的对象存储在一个新的列表permissionDirectoryResVO中。 第五步:写一个获取子集的方法体 public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){ return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList()); } 具体解释如下: forEach(e -> {...}):是list对象的一个方法,用于遍历该列表(或集合)中的每个元素,并对每个元素执行一段操作。 e -> {...}是一个Lambda表达式,表示对每个元素执行的操作,相当于e就是PermissionDirectoryResVO元素对象 因此,这段代码就是通过传递一个主键id和一个PermissionDirectoryResVO集合对象参数,然后遍历循环PermissionDirectoryResVO对象集合,把每一个对象的父目录id和传递过来的参数id进行对比,如果父目录id等于参数id就把这个对象收集到新的集合中,最后作为参数返回。 第六步:遍历全部数据,利用递归思想,获取全部的子集 pdr.forEach(e ->{ List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr); e.setAuthMenuList(pdrList != null ? pdrList : null); }); 具体解释如下: List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);这一步通过调用第五步写好的方法已经获取到了全部子集,就是说,如果所有数据一集目录有三个,分别是1、2、3,那么当循环完的时候会有3个pdrList集合,每个集合中分别装有1目录下的数据、2目录下的数据、3目录下的数据。 当每一次循环的时候,都会对pdr集合中的元素进行一次判断,e.setAuthMenuList(pdrList != null ? pdrList : null);使用三目运算符,如果pdrList集合不为空就表示当前元素有子集,然把pdrList集合赋值给元素的authMenuList属性,如果为空就表示没有子集,赋值空就可以。 当集合遍历完毕,数据情况看图①实例 第七步:获取所有顶点数据 List<PermissionDirectoryResVO> parentNodes = pdr.stream(). filter(e -> e.getParentId().equals(0)).collect(Collectors.toList()); directoryTree.addAll(parentNodes); 具体解释如下: 判断pdr集合中父目录id为0的数据,然后赋值给新的parentNodes,最后把这个集合存进directoryTree集合容器中
图①
②第二种写法
public List<PermissionDirectoryResVO> searchMenu() { List<PermissionDirectoryResVO> directoryTree = new ArrayList<>(); // 获取全部数据 List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList(); // 创建存储PermissionDirectoryResVO对象的集合容器 List<PermissionDirectoryResVO> pdr = new ArrayList<>(); // 判断集合中数据是否为空,不为空进行树结构排列 if (CollectionUtil.isNotEmpty(menuList)){ // 遍历循环集合menuList元素赋值给pdr集合中元素对象,这里就是第一种写法的第四步 for (PermissionDirectory permissionDirectory : menuList){ PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO(); permissionDirectoryResVO.setId(permissionDirectory.getId()); permissionDirectoryResVO.setParentId(permissionDirectory.getParentId()); permissionDirectoryResVO.setMenuName(permissionDirectory.getMenuName()); permissionDirectoryResVO.setMenuLevel(permissionDirectory.getMenuLevel()); permissionDirectoryResVO.setRoute(permissionDirectory.getRoute()); pdr.add(permissionDirectoryResVO); } } // 遍历全部数据,利用递归思想,获取全部的子集,第一种写法的第六步 for (PermissionDirectoryResVO e : pdr){ List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr); e.setAuthMenuList(pdrList != null ? pdrList : null); } // 获取所有顶点数据 for (PermissionDirectoryResVO e : pdr){ if (e.getParentId().equals(0)){ directoryTree.add(e); } } return directoryTree; } /** * 获取全部子集 * @param id * @param list * @return */ public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){ List<PermissionDirectoryResVO> pdr = new ArrayList<>(); // 这里就是第一种写法的第五步 for (PermissionDirectoryResVO per : list){ if (per.getParentId().equals(id)){ pdr.add(per); } } return pdr; } }
最终结果
{ "code": 200, "msg": "操作成功", "data": [ { "id": 3, "parentId": 0, "menuName": "操作手册", "menuLevel": 1, "route": "/role/manage", "authMenuList": [ { "id": 8, "parentId": 3, "menuName": "API接口文档", "menuLevel": 2, "route": "/api/documentation", "authMenuList": [ { "id": 9, "parentId": 8, "menuName": "操作手册", "menuLevel": 3, "route": "/operation/manual", "authMenuList": [] } ] }, { "id": 7, "parentId": 3, "menuName": "定时任务", "menuLevel": 2, "route": "/task/schedule", "authMenuList": [] }, { "id": 6, "parentId": 3, "menuName": "日志管理", "menuLevel": 2, "route": "/log/manage", "authMenuList": [] } ] }, { "id": 2, "parentId": 0, "menuName": "系统设置", "menuLevel": 1, "route": "/user/manage", "authMenuList": [ { "id": 5, "parentId": 2, "menuName": "用户管理", "menuLevel": 2, "route": "/system/setting", "authMenuList": [] }, { "id": 4, "parentId": 2, "menuName": "菜单管理", "menuLevel": 2, "route": "/menu/manage", "authMenuList": [] } ] }, { "id": 1, "parentId": 0, "menuName": "首页", "menuLevel": 1, "route": "/index", "authMenuList": [] } ] }
以上就是Java中实现树形菜单的两种方式的详细内容,更多关于Java实现树形菜单的资料请关注脚本之家其它相关文章!