RuoYi菜单管理:动态路由权限配置详解

RuoYi菜单管理:动态路由权限配置详解

【免费下载链接】RuoYi 🎉 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/yangzongzhuan/RuoYi

引言

在企业级应用开发中,权限管理是确保系统安全性的核心环节。RuoYi作为一款基于SpringBoot的权限管理系统,其菜单管理和动态路由权限配置机制设计精巧、功能强大。本文将深入剖析RuoYi的菜单权限体系,从数据结构到实现原理,为您全面解析动态路由权限配置的最佳实践。

菜单数据结构设计

SysMenu实体类核心字段

RuoYi的菜单系统采用层次化设计,SysMenu实体类定义了完整的菜单属性:

public class SysMenu extends BaseEntity {
    private Long menuId;          // 菜单ID
    private String menuName;      // 菜单名称
    private Long parentId;        // 父菜单ID
    private String orderNum;      // 显示顺序
    private String url;           // 菜单URL
    private String target;        // 打开方式
    private String menuType;      // 类型(M目录 C菜单 F按钮)
    private String visible;       // 菜单状态(0显示 1隐藏)
    private String perms;         // 权限字符串
    private String icon;          // 菜单图标
    private List<SysMenu> children; // 子菜单列表
}

菜单类型说明

类型代码说明用途
目录M用于组织菜单的容器构建菜单层级结构
菜单C具体的功能页面用户可访问的功能模块
按钮F页面内的操作按钮控制细粒度权限

权限配置流程

1. 数据库表设计

RuoYi使用三张核心表管理菜单权限:

  • sys_menu:存储菜单基本信息
  • sys_role:角色信息表
  • sys_role_menu:角色菜单关联表
-- 查询用户菜单权限的SQL
SELECT DISTINCT m.menu_id, m.parent_id, m.menu_name, m.url, m.visible, 
       IFNULL(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num
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
WHERE ur.user_id = #{userId} 
  AND m.menu_type IN ('M', 'C') 
  AND m.visible = 0
  AND ro.status = 0
ORDER BY m.parent_id, m.order_num

2. 动态权限加载机制

RuoYi采用Apache Shiro作为安全框架,通过自定义UserRealm实现动态权限加载:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
    SysUser user = ShiroUtils.getSysUser();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
    // 管理员拥有所有权限
    if (user.isAdmin()) {
        info.addRole("admin");
        info.addStringPermission("*:*:*");
    } else {
        // 动态查询用户角色和权限
        Set<String> roles = roleService.selectRoleKeys(user.getUserId());
        Set<String> menus = menuService.selectPermsByUserId(user.getUserId());
        
        info.setRoles(roles);
        info.setStringPermissions(menus);
    }
    return info;
}

动态路由实现原理

前端路由生成流程

mermaid

菜单数据转换处理

在后端服务中,菜单数据经过多层处理:

  1. 数据查询:根据用户ID查询有权限的菜单
  2. 树形结构构建:将扁平数据转换为层次结构
  3. 权限过滤:过滤掉无权限的菜单项
  4. 排序处理:按照order_num进行排序
// 构建菜单树形结构的关键方法
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;
}

权限缓存与更新机制

Shiro权限缓存

RuoYi使用Shiro的缓存机制提升性能,同时提供缓存清理方法:

// 清理所有用户授权信息缓存
public static void clearAllCachedAuthorizationInfo() {
    getUserRealm().clearAllCachedAuthorizationInfo();
}

// 在菜单增删改时调用缓存清理
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysMenu menu) {
    // ... 业务逻辑
    AuthorizationUtils.clearAllCachedAuthorizationInfo(); // 清理缓存
    return toAjax(menuService.insertMenu(menu));
}

缓存更新策略

操作类型缓存影响处理方式
菜单新增需要更新立即清理权限缓存
菜单修改需要更新立即清理权限缓存
菜单删除需要更新立即清理权限缓存
角色权限变更需要更新立即清理权限缓存

前端菜单渲染

动态选项卡实现

前端通过createMenuItem函数动态创建菜单选项卡:

function createMenuItem(dataUrl, menuName, isRefresh) {
    var topWindow = $(window.parent.document);
    
    // 检查选项卡是否已存在
    $('.menuTab', topWindow).each(function() {
        if ($(this).data('id') == dataUrl) {
            // 激活已存在的选项卡
            $(this).addClass('active').siblings('.menuTab').removeClass('active');
            if (isRefresh) {
                refreshTab(); // 刷新选项卡内容
            }
            return false;
        }
    });
    
    // 创建新的选项卡和iframe
    var str = '<a href="javascript:;" class="active menuTab noactive" data-id="' + dataUrl + '">' + menuName + ' <i class="fa fa-times-circle"></i></a>';
    var str1 = '<iframe class="RuoYi_iframe" src="' + dataUrl + '" frameborder="0" data-id="' + dataUrl + '" seamless></iframe>';
    
    $('.menuTab', topWindow).removeClass('active');
    $('.mainContent', topWindow).append(str1);
    $('.menuTabs .page-tabs-content', topWindow).append(str);
}

权限控制实现

前端通过Thymeleaf的shiro标签进行权限控制:

<a class="btn btn-success" onclick="$.operate.add(0)" shiro:hasPermission="system:menu:add">
    <i class="fa fa-plus"></i> 新增
</a>
<a class="btn btn-primary" onclick="$.operate.edit()" shiro:hasPermission="system:menu:edit">
    <i class="fa fa-edit"></i> 修改
</a>

最佳实践与配置示例

1. 菜单配置示例

-- 系统管理目录
INSERT INTO sys_menu VALUES (1, '系统管理', 0, '1', '#', 'menuItem', 'M', '0', NULL, 'fa fa-gear', NOW(), 'admin', NULL, NULL);

-- 用户管理菜单
INSERT INTO sys_menu VALUES (2, '用户管理', 1, '1', '/system/user', 'menuItem', 'C', '0', 'system:user:view', 'fa fa-user', NOW(), 'admin', NULL, NULL);

-- 用户管理相关按钮权限
INSERT INTO sys_menu VALUES (3, '用户查询', 2, '1', '#', 'menuItem', 'F', '0', 'system:user:list', '#', NOW(), 'admin', NULL, NULL);
INSERT INTO sys_menu VALUES (4, '用户新增', 2, '2', '#', 'menuItem', 'F', '0', 'system:user:add', '#', NOW(), 'admin', NULL, NULL);
INSERT INTO sys_menu VALUES (5, '用户修改', 2, '3', '#', 'menuItem', 'F', '0', 'system:user:edit', '#', NOW(), 'admin', NULL, NULL);

2. 权限字符串规范

RuoYi采用统一的权限字符串格式:模块:功能:操作

  • system:user:view - 系统模块用户功能的查看权限
  • system:user:add - 系统模块用户功能的新增权限
  • system:user:edit - 系统模块用户功能的修改权限
  • system:user:remove - 系统模块用户功能的删除权限

3. 动态路由配置建议

# 应用配置示例
ruoyi:
  shiro:
    user: 
      admin: true    # 是否管理员账号
    anonUrl: /login  # 匿名访问URL
    filterChainDefinitions: # 过滤链定义
      /login = anon
      /logout = logout
      /static/** = anon
      /druid/** = anon
      /websocket = anon
      /** = user

常见问题与解决方案

1. 权限缓存不更新

问题:修改菜单权限后,用户需要重新登录才能生效。

解决方案

// 在菜单管理Controller中调用缓存清理
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(@Validated SysMenu menu) {
    // ... 业务逻辑
    AuthorizationUtils.clearAllCachedAuthorizationInfo(); // 清理权限缓存
    return toAjax(menuService.updateMenu(menu));
}

2. 菜单排序混乱

问题:菜单显示顺序不符合预期。

解决方案:确保每个菜单项的order_num字段正确设置,后端查询时按parent_id, order_num排序。

3. 按钮权限不生效

问题:前端按钮仍然显示,但点击无权限操作时报错。

解决方案:在前端页面中添加shiro权限校验:

<button shiro:hasPermission="system:user:add">新增用户</button>

性能优化建议

1. 缓存策略优化

// 使用Redis集中式缓存替代本地缓存
@Configuration
public class ShiroConfig {
    
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisTemplate(redisTemplate);
        return cacheManager;
    }
}

2. 数据库查询优化

-- 添加索引提升查询性能
CREATE INDEX idx_user_role ON sys_user_role(user_id);
CREATE INDEX idx_role_menu ON sys_role_menu(role_id);
CREATE INDEX idx_menu_parent ON sys_menu(parent_id);
CREATE INDEX idx_menu_visible ON sys_menu(visible);

3. 前端懒加载优化

对于大型菜单系统,可以采用分页加载或懒加载策略:

// 实现菜单懒加载
function loadMenuLazily(parentId, callback) {
    $.get('/system/menu/lazy?parentId=' + parentId, function(result) {
        if (result.code === 200) {
            callback(result.data);
        }
    });
}

总结

RuoYi的菜单管理和动态路由权限配置体系提供了一个完整、灵活且高效的权限管理解决方案。通过深入理解其设计原理和实现机制,开发者可以:

  1. 快速构建复杂的权限管理系统
  2. 灵活配置多层次的菜单结构
  3. 精细控制到按钮级别的权限
  4. 动态更新权限配置而不需要重启应用
  5. 优化性能通过合理的缓存策略

该体系的核心优势在于其松耦合的设计,菜单数据、角色权限、用户关联相互独立,使得系统具有良好的扩展性和维护性。无论是小型项目还是大型企业应用,RuoYi的权限管理系统都能提供可靠的安全保障和良好的用户体验。

【免费下载链接】RuoYi 🎉 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 【免费下载链接】RuoYi 项目地址: https://gitcode.com/yangzongzhuan/RuoYi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值