完整示例代码地址如下:
https://github.com/Dr-Water/springboot-action/tree/master/springboot-shiro
一、 权限树的问题由来
- 在开发中难免遇到一个有多级菜单结构树,或者多级部门的结构树,亦或是省市区县的多级结构,数据结构类似如下的json数据:
[ { "id": "1", "name": "主菜单1", "pid": "0", "menuChildren": [ { "id": "4", "name": "子菜单1.1", "pid": "1", "menuChildren": [ { "id": "6", "name": "子菜单1.1.1", "pid": "4", "menuChildren": [] }, { "id": "9", "name": "子菜单1.1.2", "pid": "4", "menuChildren": [] } ] }, { "id": "5", "name": "子菜单1.2", "pid": "1", "menuChildren": [] } ] }, { "id": "2", "name": "主菜单2", "pid": "0", "menuChildren": [ { "id": "7", "name": "子菜单2.1", "pid": "2", "menuChildren": [] }, { "id": "8", "name": "子菜单2.2", "pid": "2", "menuChildren": [] } ] }, { "id": "3", "name": "主菜单3", "pid": "0", "menuChildren": [] } ]
二、 解决方案
目前的解决方案主要有以下两种方案:
- 方案一:后端把所有需要的数据以一个大list返回前端,前端进行操作,把数据搞成树状结构
- 方案二: 后端在后端返回数据之前把数据搞成已经有层次结构的数据,方案二也分为两种解决方法
- 方法一:次性将数据查询出来,在java程序中进行树状结构的构建
- 方法二: 第一次将最高层次的数据查询出来,然后多次循环查询数据库将子数据查询出来
由于博主的前端水平有限,目前只能用后端的实现方式,再加上每次查询数据库的开销比较大,所以本文使用方案二的方法一进行验证
实现步骤
以菜单的结构树为例
- 准备mysql数据库的基础数据
- java的实体类:
@Data @NoArgsConstructor public class Menu implements Serializable { private String id; private String name; private String pid; private List<Menu> menuChildren; }
- java的dao层
@Mapper public interface MenuDao { /** * 根据父类id查询子类菜单 * @param pid * @return */ List<Menu> selectByPid(Integer pid); /** * 查询所有的菜单 * @return */ List<Menu> selectAll(); /** * 查询除了一级菜单以外的菜单 * @return */ List<Menu> selectAllNotBase(); }
- mapper文件
<?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.ratel.shiro.dao.MenuDao"> <select id="selectByPid" resultType="com.ratel.shiro.entity.Menu"> SELECT * FROM menu WHERE pid=#{pid} </select> <select id="selectAll" resultType="com.ratel.shiro.entity.Menu"> SELECT * FROM menu </select> <select id="selectAllNotBase" resultType="com.ratel.shiro.entity.Menu"> SELECT * FROM menu where pid!= 0 </select> </mapper>
- Controller层(由于是查询操作,并且没有复杂的操作,偷个懒就不写service层)
@RestController @RequestMapping("mymenu") public class MenuController { @Autowired private MenuDao menuDao; @RequestMapping("/getMenuTree") public List<Menu> getMenuTree(){ List<Menu> menusBase = menuDao.selectByPid(0); List<Menu> menuLNotBase = menuDao.selectAllNotBase(); for (Menu menu : menusBase) { List<Menu> menus = iterateMenus(menuLNotBase, menu.getId()); menu.setMenuChildren(menus); } return menusBase; } /** *多级菜单查询方法 * @param menuVoList 不包含最高层次菜单的菜单集合 * @param pid 父类id * @return */ public List<Menu> iterateMenus(List<Menu> menuVoList,String pid){ List<Menu> result = new ArrayList<Menu>(); for (Menu menu : menuVoList) { //获取菜单的id String menuid = menu.getId(); //获取菜单的父id String parentid = menu.getPid(); if(StringUtils.isNotBlank(parentid)){ if(parentid.equals(pid)){ //递归查询当前子菜单的子菜单 List<Menu> iterateMenu = iterateMenus(menuVoList,menuid); menu.setMenuChildren(iterateMenu); result.add(menu); } } } return result; } }
- 启动程序用postman进行测试:
返回的json数据如下:
[ { "id": "1", "name": "主菜单1", "pid": "0", "menuChildren": [ { "id": "4", "name": "子菜单1.1", "pid": "1", "menuChildren": [ { "id": "6", "name": "子菜单1.1.1", "pid": "4", "menuChildren": [] }, { "id": "9", "name": "子菜单1.1.2", "pid": "4", "menuChildren": [] } ] }, { "id": "5", "name": "子菜单1.2", "pid": "1", "menuChildren": [] } ] }, { "id": "2", "name": "主菜单2", "pid": "0", "menuChildren": [ { "id": "7", "name": "子菜单2.1", "pid": "2", "menuChildren": [] }, { "id": "8", "name": "子菜单2.2", "pid": "2", "menuChildren": [] } ] }, { "id": "3", "name": "主菜单3", "pid": "0", "menuChildren": [] } ]
参考链接: