1
路由 开源框架若依系统中,其前端菜单列表,即我们技术上通常所说的路由信息,是通过接口来返回给前端动态生成的。我们通过抓取接口发现,其接口路径为:getRouters
。今天我们来通过接口代码,逐渐深入来研究若依框架中其菜单表设计结构。
getRouters 该接口位于ruoyi-admin模块controller包下的SysLoginController中,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("getRouters") public AjaxResult getRouters () { Long userId = SecurityUtils.getUserId(); List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId); return AjaxResult.success(menuService.buildMenus(menus)); }
代码第一行,是后台程序通过过滤器等来逐步获取用户的userId,这一步我们不多做阐述。
第二行,通过userId获取菜单树结构,我们来深入看一下其中的执行逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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 ); }
其执行逻辑为:首先判断当前用户是否是超级管理员admin,如果不是,则通过方法:selectMenuTreeByUserId来查询用户的菜单。我们暂且放一下最后一句话,先看这一句:
1 menus = menuMapper.selectMenuTreeByUserId(userId);
其对应的执行mysql语句为:
1 2 3 4 5 6 7 8 select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time from sys_menu m left join sys_role_menu rm on m.menu_id = rm.menu_id left join sys_user_role ur on rm.role_id = ur.role_id left join sys_role ro on ur.role_id = ro.role_id left join sys_user u on ur.user_id = u.user_id where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0 AND ro.status = 0 order by m.parent_id, m.order_num
跟权限类似,其查询逻辑为:用户 -> 用户角色对应表->用户角色->角色菜单对应表->菜单。除一般的有效查询条件外(即state=1或者status=0,当然,如果要说的话,就是很奇怪为什么要把0设为正常,把1设为失效或停用),我们来看下面这一查询条件:
1 m.menu_type in ('M', 'C')
我们通过数据库,查看其数据表设计,其字段内容menu_type
,如下:
即查询菜单的时候,只查询目录以及菜单,而不查询按钮类别。
如此,我们便把指定用户可以查看的所有目录,菜单都罗列了出来,反过来,我们再去看那一行代码:
1 getChildPerms(menus, 0 );
深入方法内部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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(); if (t.getParentId() == parentId) { recursionFn(list, t); returnList.add(t); } } return returnList; }
不难看出,其实现主要是将菜单的链表list转换为一个“目录-菜单”的树状结构。
到这里结束了吗?不我们还有Controller中的最后一句没有看:
1 return AjaxResult.success(menuService.buildMenus(menus));
前面的AjaxResult.success(xxx)我们不阐述,其表示成功返回数据,我们展开方法buildMenus(menus)
看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @Override public List<RouterVo> buildMenus (List<SysMenu> menus) { List<RouterVo> routers = new LinkedList <RouterVo>(); for (SysMenu menu : menus) { RouterVo router = new RouterVo (); router.setHidden("1" .equals(menu.getVisible())); router.setName(getRouteName(menu)); router.setPath(getRouterPath(menu)); router.setComponent(getComponent(menu)); router.setQuery(menu.getQuery()); router.setMeta(new MetaVo (menu.getMenuName(), menu.getIcon(), StringUtils.equals("1" , menu.getIsCache()), menu.getPath())); List<SysMenu> cMenus = menu.getChildren(); if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { router.setAlwaysShow(true ); 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("/inner" ); List<RouterVo> childrenList = new ArrayList <RouterVo>(); RouterVo children = new RouterVo (); String routerPath = StringUtils.replaceEach(menu.getPath(), new String [] { Constants.HTTP, Constants.HTTPS }, new String [] { "" , "" }); 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); } return routers; }
这里对菜单的判断主要有以下几种:
子菜单非空的“目录”
外链
路由地址为http开头的内链(即isInnerLink
执行的判断)
如此,便成功的将存在数据库中的目录菜单,转换为了vue前端需要的路由地址映射结构,具体的,我们来看一下,前端的路由结构样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 { path : '' , component : Layout , redirect : 'index' , children : [ { path : 'index' , component : (resolve ) => require (['@/views/index' ], resolve), name : 'Index' , meta : { title : '首页' , icon : 'dashboard' , affix : true } } ] }