RuoYi-Vue源码阅读(三):用户相关模块

news2024/9/22 17:17:31

文章目录

  • 1 用户角色权限信息 getInfo
    • 1.1 后端代码实现步骤
      • 1.1.1 获取用户角色权限信息
      • 1.1.2 获取用户的角色集合
    • 1.2 前端代码实现步骤
  • 2 获取用户路由信息 getRouters
    • 2.1 后端代码实现步骤
      • 2.1.1 获取菜单目录
      • 2.1.2 构建前端路由所需要的菜单
    • 2.2 前端代码实现步骤
  • 3 参考链接

1 用户角色权限信息 getInfo

1.1 后端代码实现步骤

用户角色权限信息模块的Controller层的代码位于com.ruoyi.web.controller.system包下的SysLoginController.java

/**
 * 获取用户信息
 * 
 * @return 用户信息
 */
@GetMapping("getInfo")
public AjaxResult getInfo()
{
    // 通过SecurityUtils.getLoginUser()获取当前登录用户的信息
    SysUser user = SecurityUtils.getLoginUser().getUser();
    
    // 调用permissionService的getRolePermission方法获取用户的角色集合
    Set<String> roles = permissionService.getRolePermission(user);
    
    // 调用permissionService的getMenuPermission方法获取用户的权限集合
    Set<String> permissions = permissionService.getMenuPermission(user);
    
    // 创建一个AjaxResult对象,表示一个通用的Ajax请求响应
    AjaxResult ajax = AjaxResult.success();
    
    // 将用户信息、角色集合和权限集合放入AjaxResult对象中
    ajax.put("user", user);
    ajax.put("roles", roles);
    ajax.put("permissions", permissions);
    
    // 返回AjaxResult对象
    return ajax;
}

该方法的主要目的是获取当前登录用户的信息,包括用户信息、角色集合和权限集合,并将这些信息封装在一个AjaxResult对象中返回。这个AjaxResult对象通常用于表示一个通用的Ajax请求响应,其中包含了请求的状态(成功或失败)和响应的数据。

代码的逻辑如下:

  1. 调用SecurityUtils.getLoginUser()方法获取当前登录用户的信息。
  2. 调用permissionServicegetRolePermission方法,传入当前用户信息,获取用户的角色集合。
  3. 调用permissionServicegetMenuPermission方法,传入当前用户信息,获取用户的权限集合。
  4. 创建一个AjaxResult对象,表示一个通用的Ajax请求响应,并设置其状态为成功。
  5. 将用户信息、角色集合和权限集合放入AjaxResult对象中。
  6. 返回AjaxResult对象。

用户角色权限流程图

1.1.1 获取用户角色权限信息

调用permissionServicegetRolePermission方法,该方法的主要目的是获取用户的角色权限信息。如果是管理员,则直接返回包含"admin"角色的集合。如果不是管理员,则通过用户ID到数据库查询用户的角色权限,并将这些角色权限信息返回。

/**
 * 获取角色数据权限
 * 
 * @param user 用户信息
 * @return 角色权限信息
 */
public Set<String> getRolePermission(SysUser user)
{
    // 创建一个HashSet来存储角色权限信息
    Set<String> roles = new HashSet<String>();
    
    // 检查用户是否为管理员
    if (user.isAdmin())
    {
        // 如果是管理员,添加"admin"角色到集合中
        roles.add("admin");
    }
    else
    {
        // 如果不是管理员,通过用户ID查询用户的角色权限
        // 并将查询到的角色权限添加到集合中
        roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
    }
    
    // 返回包含角色权限信息的集合
    return roles;
}

其代码整体逻辑如下:

  1. 创建一个HashSet对象roles来存储角色权限信息。
  2. 检查用户是否为管理员。如果是管理员,则将字符串"admin"添加到roles集合中。
  3. 如果用户不是管理员,则调用roleServiceselectRolePermissionByUserId方法,传入用户的ID,从数据库查询出用户的角色权限信息。
  4. 将获取到的角色权限信息添加到roles集合中。
  5. 返回包含角色权限信息的roles集合。

其中,roleServiceselectRolePermissionByUserId方法的代码如下:

/**
 * 根据用户ID查询权限
 * 
 * @param userId 用户ID
 * @return 权限列表
 */
@Override
public Set<String> selectRolePermissionByUserId(Long userId)
{
    // 调用roleMapper的selectRolePermissionByUserId方法,根据用户ID查询用户的角色列表
    List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId);
    
    // 创建一个HashSet来存储权限字符串
    Set<String> permsSet = new HashSet<>();
    
    // 遍历查询到的角色列表
    for (SysRole perm : perms)
    {
        // 如果角色不为空
        if (StringUtils.isNotNull(perm))
        {
            // 将角色中的权限字符串(以逗号分隔)拆分成数组,然后将数组转换为列表,并添加到权限Set中
            permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
        }
    }
    
    // 返回查询到的权限Set
    return permsSet;
}

这个方法的整体逻辑是:首先根据用户ID查询用户的角色列表,然后遍历角色列表,将每个角色的权限字符串(可能包含多个权限,以逗号分隔)拆分成单个权限,并添加到一个Set集合中。最后返回这个Set集合,即用户的所有权限。

1.1.2 获取用户的角色集合

调用permissionServicegetMenuPermission方法,该方法的目的是根据用户的角色和权限,返回用户可以访问的菜单权限信息。

/**
 * 获取菜单数据权限
 * 
 * @param user 用户信息
 * @return 菜单权限信息
 */
public Set<String> getMenuPermission(SysUser user)
{
    // 创建一个HashSet来存储菜单权限信息
    Set<String> perms = new HashSet<String>();
    
    // 如果用户是管理员,则拥有所有权限
    if (user.isAdmin())
    {
        perms.add("*:*:*");
    }
    else
    {
        // 获取用户的角色列表
        List<SysRole> roles = user.getRoles();
        
        // 如果用户的角色列表不为空
        if (!CollectionUtils.isEmpty(roles))
        {
            // 遍历用户的角色列表
            for (SysRole role : roles)
            {
                // 根据角色ID查询菜单权限
                Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
                
                // 将查询到的权限添加到角色对象的permissions属性中
                role.setPermissions(rolePerms);
                
                // 将查询到的权限添加到权限Set中
                perms.addAll(rolePerms);
            }
        }
        else
        {
            // 如果用户没有角色,则根据用户ID查询菜单权限
            perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
        }
    }
    
    // 返回查询到的菜单权限信息Set
    return perms;
}

其代码整体逻辑如下:

  1. 首先创建一个HashSet来存储菜单权限信息。
  2. 如果用户是管理员,则直接添加"\*:\*:\*"权限到Set中,表示拥有所有权限。
  3. 如果用户不是管理员,则获取用户的角色列表。
  4. 如果用户有角色,则遍历角色列表,根据每个角色的ID查询菜单权限,并将查询到的权限添加到角色对象的permissions属性中,以及添加到权限Set中。
  5. 如果用户没有角色,则根据用户ID查询菜单权限,并将查询到的权限添加到权限Set中。
  6. 最后返回查询到的菜单权限信息Set。

其中,menuServiceselectMenuPermsByRoleId方法的代码如下:

/**
 * 根据角色ID查询菜单权限
 * 
 * @param roleId 角色ID
 * @return 菜单权限列表
 */
@Override
public Set<String> selectMenuPermsByRoleId(Long roleId)
{
    // 调用菜单映射器的selectMenuPermsByRoleId方法,根据角色ID查询菜单权限列表
    List<String> perms = menuMapper.selectMenuPermsByRoleId(roleId);
    
    // 创建一个HashSet来存储菜单权限字符串
    Set<String> permsSet = new HashSet<>();
    
    // 遍历查询到的菜单权限列表
    for (String perm : perms)
    {
        // 如果菜单权限字符串不为空
        if (StringUtils.isNotEmpty(perm))
        {
            // 将菜单权限字符串(以逗号分隔)拆分成数组,然后将数组转换为列表,并添加到权限Set中
            permsSet.addAll(Arrays.asList(perm.trim().split(",")));
        }
    }
    
    // 返回查询到的菜单权限Set
    return permsSet;
}

这个方法的整体逻辑是:首先根据角色ID查询角色的菜单权限列表,然后遍历权限列表,将每个权限字符串(可能包含多个权限,以逗号分隔)拆分成单个权限,并添加到一个Set集合中。最后返回这个Set集合,即角色的所有菜单权限。

menuServiceselectMenuPermsByUserId方法的代码如下:

/**
 * 根据用户ID查询菜单权限
 * 
 * @param userId 用户ID
 * @return 菜单权限列表
 */
@Override
public Set<String> selectMenuPermsByUserId(Long userId)
{
    // 调用菜单映射器的selectMenuPermsByUserId方法,根据用户ID查询菜单权限列表
    List<String> perms = menuMapper.selectMenuPermsByUserId(userId);
    
    // 创建一个HashSet来存储菜单权限字符串
    Set<String> permsSet = new HashSet<>();
    
    // 遍历查询到的菜单权限列表
    for (String perm : perms)
    {
        // 如果菜单权限字符串不为空
        if (StringUtils.isNotEmpty(perm))
        {
            // 将菜单权限字符串(以逗号分隔)拆分成数组,然后将数组转换为列表,并添加到权限Set中
            permsSet.addAll(Arrays.asList(perm.trim().split(",")));
        }
    }
    
    // 返回查询到的菜单权限Set
    return permsSet;
}

这个方法的整体逻辑是:首先根据用户ID查询用户的菜单权限列表,然后遍历权限列表,将每个权限字符串(可能包含多个权限,以逗号分隔)拆分成单个权限,并添加到一个Set集合中。最后返回这个Set集合,即用户的所有菜单权限。

最终将查询到信息封装在一个AjaxResult对象中返回返回给前端,完成getInfo

1.2 前端代码实现步骤

因为getInfo是获取用户的信息,所以为了保证正常的使用,进入每个界面都会调用getInfo,设置在全局路由 promission.js中。

// 引入路由实例
import router from './router'
// 引入Vuex存储实例
import store from './store'
// 引入Element UI的Message组件
import { Message } from 'element-ui'
// 引入NProgress进度条库
import NProgress from 'nprogress'
// 引入NProgress的CSS样式
import 'nprogress/nprogress.css'
// 引入用于获取token的工具函数
import { getToken } from '@/utils/auth'
// 引入用于处理重新登录的工具函数
import { isRelogin } from '@/utils/request'

// 配置NProgress进度条
NProgress.configure({ showSpinner: false })

// 定义免登录白名单路由
const whiteList = ['/login', '/register']

// 路由守卫,在每次路由跳转前执行
router.beforeEach((to, from, next) => {
  // 开始NProgress进度条
  NProgress.start()
  // 检查是否有token
  if (getToken()) {
    // 如果有token,检查路由元信息是否有title
    if (to.meta.title) {
      // 如果有title,则更新Vuex中的title
      store.dispatch('settings/setTitle', to.meta.title)
    }
    // 如果当前路由是登录页,则重定向到首页
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else if (whiteList.indexOf(to.path) !== -1) {
      // 如果当前路由在白名单中,则直接通过
      next()
    } else {
      // 如果用户角色列表为空,则尝试获取用户信息
      if (store.getters.roles.length === 0) {
        // 设置重新登录的标志为true
        isRelogin.show = true
        // 尝试获取用户信息
        store.dispatch('GetInfo').then(() => {
          // 获取用户信息成功后,设置重新登录的标志为false
          isRelogin.show = false
          // 根据用户角色生成可访问的路由
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 动态添加可访问的路由到路由表中
            router.addRoutes(accessRoutes)
            // 确保addRoutes已完成,然后跳转到目标路由
            next({ ...to, replace: true })
          })
        }).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=${encodeURIComponent(to.fullPath)}`)
      NProgress.done()
    }
  }
})

其整体逻辑如下:

  1. outer.beforeEach方法的作用就是在做每次路由前都会执行其中的内容。(这里就是每次都会执行getInfogetRoutes)。
  2. 首先启动NProgress进度条。
  3. 检查是否有token,如果没有,则检查当前路由是否在白名单中。
  4. 如果当前路由是登录页,则重定向到首页。
  5. 如果当前路由不在白名单中,则检查用户角色列表是否为空。
  6. 如果用户角色列表为空,尝试获取用户信息,并根据用户角色生成可访问的路由。
  7. 如果获取用户信息成功,则动态添加可访问的路由到路由表中,并确保addRoutes已完成,然后跳转到目标路由。
  8. 如果获取用户信息失败,则退出登录并重定向到首页。
  9. 如果用户角色列表不为空,则直接通过。
  10. 如果当前路由在白名单中,则直接通过。
  11. 如果当前路由不在白名单中,则重定向到登录页,并将目标路由作为参数传递。

user.js文件的GetInfo方法中调用getInfo方法,最终返回给前端当前的用户数据,将用户数据存储在全局存储中(这里会存储用户的角色,权限,名字,头像 ,此处为该getInfo实现的核心)

// 获取用户信息
GetInfo({ commit, state }) {
  // 返回一个新的Promise对象,用于处理异步操作
  return new Promise((resolve, reject) => {
    // 调用getInfo函数,获取用户信息
    getInfo().then(res => {
      // 从响应中获取用户对象
      const user = res.user;
      // 设置用户头像,如果用户没有设置头像或者头像为空,则使用默认头像
      const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
      // 检查返回的roles是否是一个非空数组
      if (res.roles && res.roles.length > 0) {
        // 如果有角色信息,则提交SET_ROLES mutation来更新角色状态
        commit('SET_ROLES', res.roles);
        // 提交SET_PERMISSIONS mutation来更新权限状态
        commit('SET_PERMISSIONS', res.permissions);
      } else {
        // 如果没有角色信息,则提交SET_ROLES mutation,设置默认角色
        commit('SET_ROLES', ['ROLE_DEFAULT']);
      }
      // 提交SET_ID mutation来更新用户ID状态
      commit('SET_ID', user.userId);
      // 提交SET_NAME mutation来更新用户名称状态
      commit('SET_NAME', user.userName);
      // 提交SET_AVATAR mutation来更新用户头像状态
      commit('SET_AVATAR', avatar);
      // 如果获取用户信息成功,则resolve Promise
      resolve(res);
    }).catch(error => {
      // 如果获取用户信息失败,则reject Promise
      reject(error);
    });
  });
}

其整体逻辑如下:

  1. 定义一个action:GetInfo,它是一个异步操作,返回一个Promise对象。
  2. 调用getInfo函数,这个函数可能是从API获取用户信息的异步操作。
  3. getInfo成功返回时,处理返回的用户信息:
    • 检查用户头像是否为空,如果为空则使用默认头像。
    • 检查返回的角色信息是否存在且不为空,如果存在则提交SET_ROLESSET_PERMISSIONS mutation来更新角色和权限状态。
    • 如果角色信息不存在,则提交SET_ROLES mutation,设置默认角色。
    • 提交SET_ID, SET_NAMESET_AVATAR mutation来更新用户ID、名称和头像状态。
    • 如果处理成功,则resolve Promise。
  4. 如果getInfo抛出错误,则reject Promise,并将错误传递出去。

在该方法中的getInfo方法的实现为下:

获取用户详细信息getInfo

2 获取用户路由信息 getRouters

2.1 后端代码实现步骤

用户路由信息模块的Controller层的代码位于com.ruoyi.web.controller.system包下的SysLoginController.java

/**
 * 获取路由信息
 * 
 * @return 路由信息
 */
@GetMapping("getRouters")
public AjaxResult getRouters()
{
    // 从SecurityUtils工具类中获取当前用户的ID
    Long userId = SecurityUtils.getUserId();
    
    // 使用menuService的selectMenuTreeByUserId方法,根据用户ID获取菜单树
    List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
    
    // 使用menuService的buildMenus方法,将菜单树转换为路由信息
    return AjaxResult.success(menuService.buildMenus(menus));
}

该方法的主要目的是根据用户的权限和菜单配置生成前端路由,以便前端能够动态加载和显示对应的页面和组件。

其代码的整体逻辑如下:

  1. 定义一个处理GET请求的方法getRouters,它是一个公开的API端点。
  2. SecurityUtils工具类中获取当前用户的ID。SecurityUtils是一个自定义的工具类,用于获取当前用户的安全信息,比如用户ID。
  3. 调用menuServiceselectMenuTreeByUserId方法,传入当前用户的ID,获取该用户的菜单树形结构。菜单树形结构通常表示为一个菜单项列表,每个菜单项可能有子菜单项。
  4. 调用menuServicebuildMenus方法,传入步骤3中获取的菜单树形结构,将菜单树形结构转换为前端路由所需的格式。
  5. 使用AjaxResult.success方法将步骤4中生成的路由信息包装成一个成功的Ajax响应,并返回给客户端。

用户路由信息流程图

2.1.1 获取菜单目录

调用menuServiceselectMenuTreeByUserId方法,该方法的目的是根据用户ID查询用户的菜单,并返回一个菜单树形结构的列表。如果是管理员,则返回所有菜单的树形结构;如果是普通用户,则返回用户有权限访问的菜单的树形结构。

/**
 * 根据用户ID查询菜单
 * 
 * @param userId 用户ID
 * @return 菜单列表
 */
@Override
public List<SysMenu> selectMenuTreeByUserId(Long userId)
{
    // 声明一个SysMenu类型的列表,用于存储查询到的菜单
    List<SysMenu> menus = null;
    
    // 检查用户是否为管理员
    if (SecurityUtils.isAdmin(userId))
    {
        // 如果是管理员,则查询所有菜单
        menus = menuMapper.selectMenuTreeAll();
    }
    else
    {
        // 如果不是管理员,则根据用户ID查询用户的菜单
        menus = menuMapper.selectMenuTreeByUserId(userId);
    }
    
    // 递归调用getChildPerms方法,将菜单列表转换为菜单树形结构
    // 0是父菜单的ID,通常代表顶级菜单
    return getChildPerms(menus, 0);
}

其代码的整体逻辑是:

  1. 首先声明一个SysMenu类型的列表menus,用于存储查询到的菜单。
  2. 检查用户是否为管理员。如果是管理员,则调用menuMapperselectMenuTreeAll方法查询所有菜单。如果不是管理员,则调用menuMapperselectMenuTreeByUserId方法根据用户ID查询用户的菜单。
  3. 最后,调用getChildPerms方法将菜单列表转换为菜单树形结构,并返回这个树形结构的列表。这里的0通常代表顶级菜单的ID。

其中,getChildPerms()方法的代码如下,该方法的目的是构建菜单的树形结构,其中每个菜单项都是其父节点的子节点。通过递归调用recursionFn方法,可以获取到所有子节点,并将它们添加到返回列表中:

/**
 * 根据父节点的ID获取所有子节点
 * 
 * @param list 菜单列表
 * @param parentId 父节点ID
 * @return 子节点列表
 */
public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
{
    // 创建一个新的ArrayList来存储返回的子节点列表
    List<SysMenu> returnList = new ArrayList<SysMenu>();
    
    // 使用迭代器遍历菜单列表
    for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
    {
        // 获取当前菜单项
        SysMenu t = (SysMenu) iterator.next();
        
        // 检查当前菜单项的父节点ID是否与传入的父节点ID匹配
        if (t.getParentId() == parentId)
        {
            // 如果匹配,递归调用recursionFn方法获取当前菜单项的所有子节点
            recursionFn(list, t);
            // 将当前菜单项添加到返回列表中
            returnList.add(t);
        }
    }
    
    // 返回所有匹配的子节点列表
    return returnList;
}

/**
 * 递归列表
 * 
 * @param list 菜单列表
 * @param t 当前菜单项
 */
private void recursionFn(List<SysMenu> list, SysMenu t)
{
    // 得到当前菜单项的子节点列表
    List<SysMenu> childList = getChildList(list, t);
    // 将子节点列表设置到当前菜单项的children属性中
    t.setChildren(childList);
    // 遍历每个子节点
    for (SysMenu tChild : childList)
    {
        // 如果子节点还有子节点,递归调用recursionFn方法
        if (hasChild(list, tChild))
        {
            recursionFn(list, tChild);
        }
    }
}

/**
 * 得到子节点列表
 * 
 * @param list 菜单列表
 * @param t 当前菜单项
 * @return 子节点列表
 */
private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
{
    // 创建一个新的ArrayList来存储子节点
    List<SysMenu> tlist = new ArrayList<SysMenu>();
    // 使用迭代器遍历菜单列表
    Iterator<SysMenu> it = list.iterator();
    while (it.hasNext())
    {
        SysMenu n = (SysMenu) it.next();
        // 如果菜单项的父节点ID与当前菜单项的菜单ID匹配,则将其添加到子节点列表中
        if (n.getParentId().longValue() == t.getMenuId().longValue())
        {
            tlist.add(n);
        }
    }
    // 返回子节点列表
    return tlist;
}

/**
 * 判断是否有子节点
 * 
 * @param list 菜单列表
 * @param t 当前菜单项
 * @return 如果有子节点返回true,否则返回false
 */
private boolean hasChild(List<SysMenu> list, SysMenu t)
{
    // 获取当前菜单项的子节点列表,如果列表大小大于0,则表示有子节点
    return getChildList(list, t).size() > 0;
}

此方法的整体逻辑是:

  1. getChildPerms方法根据传入的父节点ID,遍历菜单列表,找到所有匹配的菜单项,并对每个匹配的菜单项调用recursionFn方法。
  2. recursionFn方法首先通过getChildList方法获取当前菜单项的所有子节点,然后将这些子节点设置到当前菜单项的children属性中。
  3. 对于每个子节点,如果它还有子节点,则递归调用recursionFn方法。
  4. getChildList方法遍历菜单列表,找到并返回所有与当前菜单项匹配的子节点。
  5. hasChild方法通过调用getChildList方法并检查返回列表的大小来判断当前菜单项是否有子节点。

从通过userId查询到总菜单列表中查询出该节点对应的所有菜单,将这些菜单全部设置为当前节点的子菜单,设置完毕之后,通过循环递归的方式去访问各个子节点的对应的子菜单并对其进行组装,以此类推,那些没有子菜单的菜单就不会继续递归,最终完成菜单的组装。

2.1.2 构建前端路由所需要的菜单

调用menuServicebuildMenus方法,此方法的目的是将系统菜单列表转换为前端路由列表,以便前端框架能够正确地渲染和管理页面。

/**
 * 构建前端路由所需要的菜单
 * 
 * @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();
        
        // 设置路由的隐藏属性,如果菜单可见性为"1",则隐藏该路由
        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 (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
        {
            // 设置路由始终显示
            router.setAlwaysShow(true);
            
            // 设置路由的重定向,这里设置为"noRedirect",表示不重定向
            router.setRedirect("noRedirect");
            
            // 递归调用buildMenus方法,构建子菜单的路由列表
            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);
    }
    
    // 返回生成的路由列表
    return routers;
}

这段代码的逻辑如下:

  1. 创建一个空的路由列表routers
  2. 遍历输入的菜单列表menus
  3. 对于每个菜单项,创建一个新的路由对象router,并设置其属性,如路径、组件、查询参数和元信息。
  4. 检查菜单项是否有子菜单,如果是目录类型并且有子菜单,则递归调用buildMenus方法来构建子菜单的路由列表。
  5. 如果菜单项是外链,则创建一个子路由对象,并设置其属性,但不设置元信息。
  6. 如果菜单项是内部链接,则创建一个子路由对象,并设置其属性,包括组件、名称和元信息。
  7. 将创建的路由对象添加到路由列表routers中。
  8. 返回生成的路由列表routers

最终将组装好的菜单列表封装在一个AjaxResult对象中返回返回给前端,完成getRouters

2.2 前端代码实现步骤

在界面加载时就会调用方法GenerateRoutes

调用方法GenerateRoutes

GenerateRoutes中会调用 getRouters方法,调用后端对用的getRouters接口,这个方法的作用是从后端获取路由数据,并根据这些数据生成新的路由配置。

actions: {
  // 生成路由
  GenerateRoutes({ commit }) {
    // 返回一个新的Promise对象
    return new Promise(resolve => {
      // 向后端请求路由数据
      getRouters().then(res => {
        // 将响应数据进行两次JSON解析,以避免引用问题
        const sdata = JSON.parse(JSON.stringify(res.data));
        const rdata = JSON.parse(JSON.stringify(res.data));

        // 使用filterAsyncRouter函数过滤路由数据,生成sidebarRoutes
        const sidebarRoutes = filterAsyncRouter(sdata);

        // 使用filterAsyncRouter函数过滤路由数据,生成rewriteRoutes,并添加一个通配符路由作为重定向
        const rewriteRoutes = filterAsyncRouter(rdata, false, true);
        rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true });

        // 使用filterDynamicRoutes函数过滤动态路由数据
        const asyncRoutes = filterDynamicRoutes(dynamicRoutes);

        // 将过滤后的动态路由添加到路由器中
        router.addRoutes(asyncRoutes);

        // 使用Vuex的commit方法提交SET_ROUTES mutation,设置新的路由配置
        commit('SET_ROUTES', rewriteRoutes);

        // 使用Vuex的commit方法提交SET_SIDEBAR_ROUTERS mutation,设置侧边栏路由配置
        commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes));

        // 使用Vuex的commit方法提交SET_DEFAULT_ROUTES mutation,设置默认路由配置
        commit('SET_DEFAULT_ROUTES', sidebarRoutes);

        // 使用Vuex的commit方法提交SET_TOPBAR_ROUTES mutation,设置顶部栏路由配置
        commit('SET_TOPBAR_ROUTES', sidebarRoutes);

        // 解析Promise,返回新的路由配置
        resolve(rewriteRoutes);
      });
    });
  }
}

其代码的整体逻辑如下:

  1. GenerateRoutes方法是一个异步操作,它返回一个Promise对象。
  2. 在Promise的回调函数中,它调用getRouters函数向后端请求路由数据。
  3. 请求成功后,它对响应数据进行两次JSON解析,以避免引用问题。
  4. 使用filterAsyncRouter函数对路由数据进行过滤,生成sidebarRoutesrewriteRoutes
  5. 将通配符路由{ path: '*', redirect: '/404', hidden: true }添加到rewriteRoutes中,作为重定向路由。
  6. 使用filterDynamicRoutes函数对动态路由数据进行过滤,生成asyncRoutes
  7. 使用router.addRoutes方法将asyncRoutes添加到路由器中。
  8. 使用Vuex的commit方法提交多个mutation,更新Vuex状态中的路由配置。
  9. 最后,解析Promise并返回新的路由配置rewriteRoutes。

不同用户根据角色不同 拿到不同的菜单栏 其实也就是动态路由

获取路由getRouters

3 参考链接

  1. 若依使用及源码解析(前后端分离版):https://blog.csdn.net/Ostkakah/article/details/132984838
  2. 若依框架学习(前后端分离)——(登录代码学习篇):https://blog.csdn.net/yeahyeah81/article/details/135066893

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1994061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式20-备忘录模式

设计模式20-备忘录 动机定义与结构定义结构 C代码推导优缺点应用场景总结备忘录模式和序列化备忘录模式1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 序列化1. **动机**2. **实现方式**3. **应用场景**4. **优点**5. **缺点** 对比总结 动机 在软件构建过…

Redis相关面试题(二)

一、BIT中不同命令使用的场景 Sring Seesion会话业务缓存分布式锁&#xff1a;为了防止用户同时登录多个设备进行操作 Int 计数器限流全局唯一ID Hash 电商购物车 BitMap 用户签到 List 消息队列 ZSet 排行榜 二、什么是缓存击穿&#xff0c;缓存穿透&#xff0c;…

C++--类和对象(一)

C是一个面向对象的编程语言&#xff0c;而面向对象编程有四大特性&#xff1a;封装&#xff0c;抽象&#xff0c;继承&#xff0c;多态。类和对象就是对应其中的封装&#xff0c;在C中将数据和对数据的操作都封装在一个class&#xff08;类&#xff09;的结构体中。 目录 类的…

【C++二分查找】2187. 完成旅途的最少时间

本文涉及的基础知识点 C二分查找 LeetCode2187. 完成旅途的最少时间 给你一个数组 time &#xff0c;其中 time[i] 表示第 i 辆公交车完成 一趟旅途 所需要花费的时间。 每辆公交车可以 连续 完成多趟旅途&#xff0c;也就是说&#xff0c;一辆公交车当前旅途完成后&#xf…

TCP如何建立长连接

文章目录 TCP建立长连接长连接和短连接长连接的优势TCP KEEPALIVE 心跳包心跳检测步骤 断线重连断线重连函数实现 实例服务端客户端程序功能演示效果 TCP建立长连接 长连接和短连接 长连接是指不论TCP的客户端和服务器之间是否有数据传输&#xff0c;都保持建立的TCP连接&…

Docker最佳实践(三):安装mysql

大家好&#xff0c;欢迎各位工友。 本篇呢我们就来演示一下如何在Docker中部署MySQL容器&#xff0c;可以按照以下步骤进行&#xff1a; 1. 搜索镜像 首先搜索MySQL镜像&#xff0c;可以使用以下命令&#xff1a; docker search mysql2. 拉取镜像 根据需求选择MySQL或Maria…

Oracle|DM 常用|不常用 SQL大口袋

目录 一、前言 二、SQL写法 1、sql获取某一条数据中的前一条和后一条 2、实现like多个值的查询&#xff08;Oracle和dm支持&#xff0c;MySQL未试过&#xff09; 3、start with connect by prior 使用方法 4、用hextoraw解决select、update、delete语句执行慢 5、ORA-00…

叉车数字化安全管理平台,安全管控升级,打造智慧监管新模式

近年来&#xff0c;国家和各地政府相继出台了多项政策法规&#xff0c;从政策层面推行叉车智慧监管&#xff0c;加大叉车安全监管力度。同时鼓励各地结合实际&#xff0c;积极探索智慧叉车建设&#xff0c;实现作业人员资格认证、车辆状态认证、安全操作提醒、行驶轨迹监控等&a…

场外个股期权的投资技巧:把握机遇与风险平衡

【来源&#xff1a;期权圈&#xff0c;场外个股每日询价】 在金融投资的领域中&#xff0c;场外个股期权作为一种新兴且具有吸引力的投资工具&#xff0c;为投资者提供了独特的机会和挑战。掌握有效的投资技巧&#xff0c;对于在这个领域中取得成功至关重要。 首先&#xff0c…

C++ | Leetcode C++题解之第332题重新安排行程

题目&#xff1a; 题解&#xff1a; class Solution { public:unordered_map<string, priority_queue<string, vector<string>, std::greater<string>>> vec;vector<string> stk;void dfs(const string& curr) {while (vec.count(curr) &am…

How we design Kola

How we design Kola - ApiHugKola background, Kola a consumer driver tester frameworkhttps://apihug.com/zhCN-docs/kola/002_design_of_kola Kola background, Kola a consumer driver tester framework ​BDD 行为驱动开发(BDD)是一种软件团队工作方式,可以缩小业务人…

基于STM32F429移植UCOSIII

μC/OS-III&#xff08;Micro C OS Three&#xff09;是一个可升级的、可固化的、基于优先级的实时内核&#xff0c;它是Micrium公司出品的RTOS&#xff08;实时操作系统&#xff09;类实时操作系统的一个版本。以下是对μC/OS-III的详细描述&#xff1a; 1. 基本特性 多任务管…

智能制造与工业互联网有何关联?工业互联网如何推进智能制造?

随着信息技术的飞速发展&#xff0c;智能制造和工业互联网已经成为当今产业变革的核心。智能制造&#xff0c;以其深度融合的设计、生产、管理、营销和售后服务等环节&#xff0c;代表了先进制造模式的典范。而工业互联网则作为智能制造的神经中枢&#xff0c;通过连接机器、人…

【初阶数据结构题目】17.用栈实现队列

用栈实现队列 点击链接答题 思路&#xff1a; 定义两个栈&#xff1a;pushST&#xff08;入数据&#xff09;和popST&#xff08;出数据&#xff09; 假设我们要在队列里放123&#xff0c;出队列123 我们先在pushST里面放进去1 2 3 然后把pushST里面的数据拿到popST里面&#…

会务要闻|向绿提质:上市企业ESG评级提升

在全球市场对环境、社会和治理&#xff08;ESG&#xff09;指标的关注与日俱增的大背景下&#xff0c;中国正积极拥抱ESG理念&#xff0c;将其作为推动经济与社会全面绿色转型的催化剂&#xff0c;更被企业视为长期主义投资策略的基石。面对日益严格的国际ESG尽职调查要求&…

信息论在机器学习中的实际应用

目录 一、说明 二、什么是信息论&#xff1f; 2.1 信息论中的关键概念 2.2 熵与信息 2.3 相互信息 2.4 Kullback-Leibler 背离 三、信息论在机器学习中的应用 3.1 功能选择&#xff1a; 3.2 计算边际概率分布 3.3 决策树&#xff1a;Information 增益 3.4 评估具有 KL 背…

FastAPI+Vue3工程项目管理系统项目实战私教课 上课笔记20240808 课程和学习计划制定

学习目标 将Word和Excel做的东西放到数据库里面去工程类公司&#xff0c;甲方&#xff0c;劳务存到数据库存储的信息主要是人员的信息 基本信息&#xff1a; 人员信息&#xff0c;资料库&#xff0c;甲方的人出现在哪些项目上&#xff0c;考勤材料信息&#xff0c;进货记录&…

yolov5更换主干网络shufflent

目录 1.网络结构解析 1.1创建yolov5s_shufflent_v2_X0_5.yaml文件 2.对common.py末尾进行添加 3.修改yolo.py 1.网络结构解析 1.可以先看看shufflenet_v2的网络结构 import torch from torch import nn from torchvision import models from torchinfo import summaryclas…

利用vscode-icons-js在Vue3项目中实现文件图标展示

背景&#xff1a; 在开发文件管理系统或类似的项目时&#xff0c;我们常常需要根据文件类型展示对应的文件图标&#xff0c;这样可以提高用户体验。本文将介绍如何在Vue3项目中利用vscode-icons-js库&#xff0c;实现类似VSCode的文件图标展示效果。 先看效果&#xff1a; 一…

Flink任务提交流程和运行模式

任务提交流程 Flink 的提交流程随着部署模式、资源管理平台的不同&#xff0c;会有不同的变化。这里做进一步的抽象&#xff0c;形成一个大概高视角的任务执行流程图&#xff0c;如下&#xff1a; Flink按照集群和资源管理的划分运行模式有&#xff1a;Standalone、Flink On…