大型纪录片:学习若依源码(前后端分离版)之 “ 获取角色权限信息及动态路由”
- 获取用户信息
- 获取路由信息
承接上回,我们发现在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 对象添加到路由列表中,并返回。
在完成过这两个请求之后,进入系统之后旁边的菜单栏就会对应显示出来,用户的权限和角色也将保存到前端,简便日后的一些操作。如果哪里有没讲清楚的地方欢迎评论留言。
那么以上就是唐某的一些理解。这次的分享就到这里了。记得一键三连~( •̀ ω •́ )✧