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

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
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值