若依前后端分离版4、获取动态菜单路由

文章详细阐述了前端Vue项目中动态菜单路由的实现过程,包括在全局路由守卫中如何处理权限检查,以及如何根据用户角色从后端获取并生成动态路由。后端部分主要介绍了Java中如何根据用户ID获取权限菜单,通过递归构建菜单树结构,最终转换为前端可用的路由格式。
摘要由CSDN通过智能技术生成


一、动态菜单路由

前端

我们发现动态路由的获取是在src\permission.js这个全局路由下的

//全局管理路由的,每个页面跳转的时候都要运行
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          isRelogin.show = false
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            router.addRoutes(accessRoutes) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

其中的GenerateRoutes就是我们要寻找的对象,在src\store\modules\permission.js下我们可以找到对应实现的方法,

actions: {
    // 生成路由
    GenerateRoutes({ commit }) {
      return new Promise(resolve => {
        // 向后端请求路由数据
        getRouters().then(res => {
          const sdata = JSON.parse(JSON.stringify(res.data))
          const rdata = JSON.parse(JSON.stringify(res.data))
          const sidebarRoutes = filterAsyncRouter(sdata)
          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
          const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
          router.addRoutes(asyncRoutes);
          commit('SET_ROUTES', rewriteRoutes)
          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
          resolve(rewriteRoutes)
        })
      })
    }
  }

根据getRouters这个方法找到位于src\api\menu.js下的

// 获取路由
export const getRouters = () => {
  return request({
    url: '/getRouters',
    method: 'get'
  })
}

后端

通过查找getRouters这个方法我们找到位于SysLoginController.java中的

@GetMapping("getRouters")
    public AjaxResult getRouters()
    {
        Long userId = SecurityUtils.getUserId();
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }

这个方法先是调用了SecurityUtils工具类中的获取ID,通过调用SecurityContextHolder.getContext().getAuthentication()获取已登陆的用户信息
建立一个List存放菜单,调用selectMenuTreeByUserId方法,我们找到接口的具体实现

@Override
    public List<SysMenu> selectMenuTreeByUserId(Long userId)
    {
        List<SysMenu> menus = null;
        if (SecurityUtils.isAdmin(userId))
        {
            menus = menuMapper.selectMenuTreeAll();
        }
        else
        {
            menus = menuMapper.selectMenuTreeByUserId(userId);
        }
        return getChildPerms(menus, 0);
    }

通过用户ID将用户角色对应拥有的菜单全部查询出来,调用getChildPerms方法

public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
    {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
        {
            SysMenu t = (SysMenu) iterator.next();
            // 根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId)
            {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

将传入的菜单进行遍历,使用迭代器将列表中的数据以此取出来,将它们的父ID和传入的parentId进行比较,一级菜单是没有parentId的所以数据库默认为0,将一开始的菜单列表和判断为一级菜单的数据传入recursionFn进行更一步比较

private void recursionFn(List<SysMenu> list, SysMenu t)
    {
        // 得到子节点列表
        List<SysMenu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenu tChild : childList)
        {
            if (hasChild(list, tChild))
            {
                recursionFn(list, tChild);
            }
        }
    }

recursionFn先是将传递过来的数据传入getChildList方法中,得到二级菜单后,将childList 赋值给一级菜单,将二级菜单中的值一一和
列表通过hasChild方法,调用getChildList方法判断是否还有二级菜单后面的三级菜单,最后进行一个嵌套循环

private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
    {
        List<SysMenu> tlist = new ArrayList<SysMenu>();
        Iterator<SysMenu> it = list.iterator();
        while (it.hasNext())
        {
            SysMenu n = (SysMenu) it.next();
            //判断n的父节点ID和t的ID是否相同
            if (n.getParentId().longValue() == t.getMenuId().longValue())
            {
                tlist.add(n);
            }
        }
        return tlist;
    }

getChildList将传递过来的列表进行一个循环,在循环中将列表的值取出来,再将每个值的ParentId这个属性取出来和传入的一级菜单的ID进行一个对比,最后返回这个一级菜单的二级菜单tlist
在查到的所有的一级菜单,二级菜单等,我们还需将它构造成树的样子

@GetMapping("getRouters")
    public AjaxResult getRouters()
    {
        Long userId = SecurityUtils.getUserId();
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }

若依使用了buildMenus来进行构建
buildMenus方法中第一段

		for (SysMenu menu : menus)
        {
            RouterVo router = new RouterVo();
            //是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现
            router.setHidden("1".equals(menu.getVisible()));
            //路由名字(menu中的路由地址)
            router.setName(getRouteName(menu));
            //路由地址(menu中的路由地址,将path前面加个/)
            router.setPath(getRouterPath(menu));
            //组件地址(menu中的组件路径)
            router.setComponent(getComponent(menu));
            //路由参数:如 {"id": 1, "name": "ry"}
            router.setQuery(menu.getQuery());
            //其他元素MetaVo
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));

使用for循环将传递过来已经排好序的菜单列表进行循环,使用getRouteName方法,getRouterPath方法,getComponent方法,将其他元素放入MetaVo中

public String getRouteName(SysMenu menu)
    {
        //将获取到的Path首字母转化为大写
        String routerName = StringUtils.capitalize(menu.getPath());
        // 非外链并且是一级目录(类型为目录)
        if (isMenuFrame(menu))
        {
            routerName = StringUtils.EMPTY;
        }
        return routerName;
    }

getRouteName将获取到的Path首字母转化为大写,使用isMenuFrame来进行判断是否为目录

public boolean isMenuFrame(SysMenu menu)
    {
        return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType())
                && menu.getIsFrame().equals(UserConstants.NO_FRAME);
    }

isMenuFrame通过父ID的值为0,菜单类型(菜单)(UserConstants.TYPE_MENU是若依定义的用户常量信息),是否菜单外链(否)

public String getRouterPath(SysMenu menu)
    {
        String routerPath = menu.getPath();
        // 内链打开外网方式
        if (menu.getParentId().intValue() != 0 && isInnerLink(menu))
        {
            routerPath = innerLinkReplaceEach(routerPath);
        }
        // 非外链并且是一级目录(类型为目录)
        if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
                && UserConstants.NO_FRAME.equals(menu.getIsFrame()))
        {
            routerPath = "/" + menu.getPath();
        }
        // 非外链并且是一级目录(类型为菜单)
        else if (isMenuFrame(menu))
        {
            routerPath = "/";
        }
        return routerPath;
    }

getRouterPath方法获取路由地址,先使用了父ID和使用isInnerLink是否为内链组件进行判断,其次使用父ID,菜单类型(目录),是否菜单外链(否),最后再通过isMenuFrame进行判断

public String getComponent(SysMenu menu)
    {
        String component = UserConstants.LAYOUT;
        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu))
        {
            component = menu.getComponent();
        }
        else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu))
        {
            component = UserConstants.INNER_LINK;
        }
        else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu))
        {
            component = UserConstants.PARENT_VIEW;
        }
        return component;
    }

getComponent,定义一个component默认为LAYOUT组件标识,先判断是否为空字符串和isMenuFrame方法判断是否为目录,将使用getComponent方法赋值给component更改为组件路径。

buildMenus第二段

			List<SysMenu> cMenus = menu.getChildren();
			if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
            {
                //当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
                router.setAlwaysShow(true);
                //重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
                router.setRedirect("noRedirect");
                //子路由
                router.setChildren(buildMenus(cMenus));
            }
            else if (isMenuFrame(menu))
            {
                router.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
                children.setQuery(menu.getQuery());
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            else if (menu.getParentId().intValue() == 0 && isInnerLink(menu))
            {
                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                router.setPath("/");
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);

当cMenus 不为空,且菜单类型为目录的时候嵌套循环,当找到菜单类型不是目录的时候使用if判断是否为菜单内部跳转,将不是目录的数据存入children,再存入列表中,再将列表放入一开始定义的router中,最后放入routers。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值