美文网首页
java后端:菜单的生成与读取

java后端:菜单的生成与读取

作者: Qiansion齐木楠雄 | 来源:发表于2021-08-23 16:51 被阅读0次

    概述

    在后端开发的过程中,我们经常会遇到一类问题,那就是关于前端页面菜单的展示,一般来说,从数据库中获取到的是一个List集合,而我们需要将这个List集合根据菜单本身的id和它的parentId来把List转换成一个Tree,然后将这个Tree交给前端的框架进行展示。本篇主要针对List集合转换成Tree并遍历输出做一个介绍。

    案例

    1、首先定义实体类
    getter和setter方法请自行定义

    public class Menu {
        // 菜单id
        private String id;
        // 菜单名称
        private String name;
        // 父菜单id
        private String parentId;
        // 菜单顺序
        private int order;
        // 子菜单
        private List<Menu> childMenus;
    }
    

    2、初始化菜单
    这里进行了简化,不从数据库里读取,直接人为制造。将数据添加到集合之后用Colletions进行了排序,也是为了模拟从数据库取出时的order by操作。

    public class MenuDao {
    
        private static ArrayList<Menu> rootMenu = new ArrayList<>();
        static {
            Menu menu = new Menu("1", "-一级菜单1", null,  0);
            Menu menu1 = new Menu ("2", "-一级菜单2", null,  1);
            Menu menu2 = new Menu ("3", "--first二级菜单2", "2",  0);
            Menu menu3 = new Menu ("4", "--second二级菜单2", "2",  1);
            Menu menu4 = new Menu ("5", "--first二级菜单1", "1",  2);
            Menu menu5 = new Menu ("6", "---first三级菜单1", "5", 0);
            Menu menu6 = new Menu ("7", "---second三级菜单1", "5", 1);
            Menu menu7 = new Menu ("8", "----first四级菜单1", "7", 0);
            rootMenu.add(menu);
            rootMenu.add(menu1);
            rootMenu.add(menu2);
            rootMenu.add(menu3);
            rootMenu.add(menu4);
            rootMenu.add(menu5);
            rootMenu.add(menu6);
            rootMenu.add(menu7);
            Collections.sort(rootMenu, new Comparator<Menu>() {
                @Override
                public int compare(Menu o1, Menu o2) {
                    return o1.getOrder() >= o2.getOrder()?1:-1;
                }
            });
        }
    
        public static ArrayList<Menu>  queryMenuList(){
            return rootMenu;
        }
    }
    

    这里根据菜单的id和parentId可以还原如下


    image.png

    需要注意此处有2个根节点,一般在项目中都是只有一个根节点的。

    3、菜单的生成
    菜单的生成主要包括两个方面
    ①找出所有根节点
    ②递归生成每个菜单的子菜单

        /**
         * 生成树形结构
         * @return
         */
        private static List<Menu> createListTree(){
            // 模拟从数据库取数据
            List<Menu> rootMenu = MenuDao.queryMenuList();
            List<Menu>  menuList = new ArrayList<>();
            for (Menu menu : rootMenu) {
                // 先找到所有根节点,根节点没有parentId
                if(menu.getParentId() == null){
                    // 以根节点为起点向下递归查找自己的子类
                    menu = buildChildTree(menu , rootMenu);
                    menuList.add(menu);
                }
            }
            return menuList;
        }
    
        /**
         * 递归装载子类
         * @param menu  当前节点
         * @param rootMenu 菜单列表
         * @return
         */
        private static Menu buildChildTree(Menu menu, List<Menu> rootMenu) {
            // 用来存放自己的子节点
            List<Menu> childMenus = new ArrayList<>();
            for (Menu menuNode : rootMenu) {
                // 如果当前节点的id 与 菜单列表中其他节点的parentId相同,就讲其放入到childMenus中
                if(menuNode.getParentId()!=null && menuNode.getParentId().equals(menu.getId())){
                    // 递归查询子节点的child
                    childMenus.add(buildChildTree(menuNode,rootMenu));
                }
            }
            // 每一个递归完成,给当前节点设置子节点
            menu.setChildMenus(childMenus);
            return menu;
        }
    

    递归有循环体和结束条件,在buildChildTree方法中结束条件不明显,因为每一次递归都会走到最后一步的return menu,所以再最终结果中所有菜单的子节点都会是[]而不是null。而且在这个递归中,其实就是从根节点开始一遍一遍的去跟菜单列表循环,找到菜单列表中parentId等于自己id的部分,但是它不是一次性就找完全部的,它找到子菜单之后,会再去看这个子菜单自己是否还会有子菜单,然后一遍遍套娃,直到它返回当前节点,然后可以继续寻找当前节点的下一个。举个例子,递归就像是看源码,一开始我们再看逻辑,突然看到一个方法,想知道它是怎么实现的,我们就点进去,然后不停的点,当我们又想继续看逻辑时,又必须一步一步的退回来。
    4、编写客户端

    public class Client {
        public static void main(String[] args) {
            List<Menu> listTree = createListTree();
            for (Menu menu : listTree) {
                System.out.println(menu);
            }
        }
    }
    

    Menu{id='1', name='-一级菜单1', parentId='null', order=0, childMenus=[Menu{id='5', name='--first二级菜单1', parentId='1', order=2, childMenus=[Menu{id='6', name='---first三级菜单1', parentId='5', order=0, childMenus=[]}, Menu{id='7', name='---second三级菜单1', parentId='5', order=1, childMenus=[Menu{id='8', name='----first四级菜单1', parentId='7', order=0, childMenus=[]}]}]}]}
    Menu{id='2', name='-一级菜单2', parentId='null', order=1, childMenus=[Menu{id='3', name='--first二级菜单2', parentId='2', order=0, childMenus=[]}, Menu{id='4', name='--second二级菜单2', parentId='2', order=1, childMenus=[]}]}
    可以看出返回的是两个根节点的部分,虽然看不出来是否的正确的(有条件的可以转化为JSON再去看)。此时还需要写一个输出根节点的方法

        /**
         * 从唯一根节点开始递归遍历
         * @param menu
         */
        private static void  OutputTreeData(Menu menu){
            System.out.println(menu.getName());
            if(menu.getChildMenus()==null || menu.getChildMenus().size()==0){
                return;
            }
            for(Menu item : menu.getChildMenus()){
                // 子节点如果还有孩子将继续递归
                OutputTreeData(item);
            }
        }
    

    修改客户端代码

    public class Client {
        public static void main(String[] args) {
            List<Menu> listTree = createListTree();
            for (Menu menu : listTree) {
                 OutputTreeData(menu);
            }
        }
    }
    

    最终输出:
    -一级菜单1
    --first二级菜单1
    ---first三级菜单1
    ---second三级菜单1
    ----first四级菜单1
    -一级菜单2
    --first二级菜单2
    --second二级菜单2

    相关文章

      网友评论

          本文标题:java后端:菜单的生成与读取

          本文链接:https://www.haomeiwen.com/subject/tndliltx.html