大型纪录片:学习若依源码(前后端分离版)之 “ 获取角色权限信息及动态路由”

承接上回,我们发现在login请求后面跟了两个请求,今天我们就来了解一下两个请求的含义。
图一

获取用户信息

先看 ‘/getInfo()’ 方法,代码如下:

/**
     * 获取用户信息
     * 
     * @return 用户信息
     */
    @GetMapping("getInfo")
    public AjaxResult getInfo()
    {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        // 角色集合
        Set<String> roles = permissionService.getRolePermission(user);
        // 权限集合
        Set<String> permissions = permissionService.getMenuPermission(user);
        AjaxResult ajax = AjaxResult.success();
        ajax.put("user", user);
        ajax.put("roles", roles);
        ajax.put("permissions", permissions);
        return ajax;
    }

在方法里面,我们首先会去spring security里面获取该用户,然后在getRolePermission(user);里面判断该用户是不是管理员,如果是管理员就给予全部权限;不是则根据用户ID去查数据库给出对应的权限。再在service里进行分割处理。

 /**
     * 根据用户ID查询权限
     * 
     * @param userId 用户ID
     * @return 权限列表
     */
    @Override
    public Set<String> selectRolePermissionByUserId(Long userId)
    {
        List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (SysRole perm : perms)
        {
            if (StringUtils.isNotNull(perm))
            {
                permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
            }
        }
        return permsSet;
    }

这里来补充一些spring security的内容,下列方法怎么获取到登录的用户信息的呢?

SecurityUtils.getLoginUser().getUser();

打开封装的SecurityUtils,可以看到getLoginUser()的具体实现
图二
Spring Security 的 getAuthentication ().getPrincipal () 方法的作用是获取当前用户的信息。这个信息通常是一个 UserDetails 的实例,它包含了用户名、密码、角色等属性。可以通过这个方法来访问当前用户的相关数据,比如判断用户是否有某个权限或者显示用户的昵称等。

这个方法的底层原理是基于 Spring Security 的核心组件 SecurityContextHolder 和 SecurityContext。

说到这我们顺便回顾一下Spring Security 的登录认证流程大致是什么样子的:

  • 首先用户通过浏览器发送一个 POST 请求到 /login 接口,请求中包含了用户名和密码等参数。
  • 服务器端接收到请求后,会调用 UsernamePasswordAuthenticationFilter 这个过滤器来处理登录逻辑。这个过滤器会从请求中提取出用户名和密码,并构造一个 UsernamePasswordAuthenticationToken 对象,这个对象就封装了用户的认证信息。

觉得我讲的不够清楚的,可以移步到这里:Spring Security 登录流程;他讲的应该比我清楚一些。

获取路由信息

获取路由信息和获取用户信息思路差不多。来看代码:

    /**
     * 获取路由信息
     * 
     * @return 路由信息
     */
    @GetMapping("getRouters")
    public AjaxResult getRouters()
    {
        Long userId = SecurityUtils.getUserId();
        //查目录表Sys_menus,根据用户查菜单,生成动态路由
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }

可能这里你会有一点点疑问,为什么什么是查出整个用户,而下面是只查出用户的ID呢?

其实打开方法一路点点点就知道,若依在设计SysUser实体的时候写了一个方法为 ‘isAdmin()’ ,该方法就是专门用来判断是否用户为管理员的。

为什么查路由只需要ID,因为它真的就只需要ID,举个例子我们有一个用户表,一张权限表,我们理所应当会再设计一个连接表,用来存放两者对应的关系。查看mapper层代码我们可以发现
图三
其实他连接了很多张表,而这些表只需要一个用户ID去查就可以了。

查完数据,会在service层进行一个处理,何为动态路由?就是一个目录下面有很多一级菜单,一级菜单又有二级菜单……以此类推。

这里给出service层的代码截图,大家可以学学别人封装的思想,我觉得也蛮有意思的。
图四
该方法为根据父节点的ID获取所有子节点

图五
该方法为递归列表,及第二层及以下。

图六
该方法用于得到子节点列表。

图七
该方法用于判断是否有子节点。

然后就把整理好后的路由菜单封装成List返回到controller层。接着执行下一个方法,这个方法也很重要,前端的菜单分为菜单、目录等等,设置可见性等操作都是通过这个方法实现的,来看方法

/**
     * 构建前端路由所需要的菜单
     *
     * @param menus 菜单列表
     * @return 路由列表
     */
    @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()));
                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;
    }

这段代码是什么意思呢?这段代码是用来构建前端路由所需要的菜单的。它的主要逻辑是:

  • 遍历菜单列表,为每个菜单创建一个 RouterVo 对象,设置其属性,如是否隐藏,名称,路径,组件,查询参数,元数据等。
  • 如果菜单有子菜单,并且菜单类型是目录(UserConstants.TYPE_DIR),则递归调用 buildMenus 方法,将子菜单也转换为 RouterVo 对象,并添加到父菜单的 children 属性中。
  • 如果菜单是一个外部链接(isMenuFrame 方法判断),则将其元数据设置为 null,并创建一个新的 RouterVo 对象作为其子菜单,设置其路径,组件,名称,元数据等。
  • 如果菜单的父菜单 id 是 0,并且是一个内部链接(isInnerLink 方法判断),则将其路径设置为 “/inner”,并创建一个新的 RouterVo 对象作为其子菜单,设置其路径(去掉 http 或 https 前缀),组件(UserConstants.INNER_LINK),名称(首字母大写),元数据等。
  • 将创建好的 RouterVo 对象添加到路由列表中,并返回。

在完成过这两个请求之后,进入系统之后旁边的菜单栏就会对应显示出来,用户的权限和角色也将保存到前端,简便日后的一些操作。如果哪里有没讲清楚的地方欢迎评论留言。

那么以上就是唐某的一些理解。这次的分享就到这里了。记得一键三连~( •̀ ω •́ )✧

Logo

快速构建 Web 应用程序

更多推荐