mysql 基于权限表的动态菜单生成指南

基于权限表的动态菜单生成指南

权限表结构设计

CREATE TABLE permissions (
    permission_id   BIGINT       PRIMARY KEY AUTO_INCREMENT,
    parent_id      BIGINT       DEFAULT 0 COMMENT '父权限ID',
    perm_name      VARCHAR(50)  NOT NULL COMMENT '权限名称',
    perm_code      VARCHAR(100) NOT NULL UNIQUE COMMENT '权限编码',
    perm_type      TINYINT     DEFAULT 1 COMMENT '1:菜单,2:按钮,3:接口',
    path           VARCHAR(200) COMMENT '前端路由路径',
    component      VARCHAR(200) COMMENT '前端组件路径',
    icon           VARCHAR(50)  COMMENT '图标',
    sort_order     INT         DEFAULT 0 COMMENT '排序',
    hidden         TINYINT     DEFAULT 0 COMMENT '是否隐藏 0:显示 1:隐藏',
    status         TINYINT     DEFAULT 1 COMMENT '1:启用,0:禁用'
);

菜单数据示例

-- 插入示例数据
INSERT INTO permissions 
(parent_id, perm_name, perm_code, perm_type, path, component, icon, sort_order) 
VALUES 
-- 一级菜单
(0, '系统管理', 'system', 1, '/system', 'Layout', 'setting', 1),
(0, '用户中心', 'user', 1, '/user', 'Layout', 'user', 2),

-- 系统管理子菜单
(1, '用户管理', 'system:user', 1, '/system/user', 'system/user/index', 'user', 1),
(1, '角色管理', 'system:role', 1, '/system/role', 'system/role/index', 'peoples', 2),
(1, '菜单管理', 'system:menu', 1, '/system/menu', 'system/menu/index', 'tree-table', 3),

-- 用户中心子菜单
(2, '个人信息', 'user:profile', 1, '/user/profile', 'user/profile/index', 'user', 1),
(2, '修改密码', 'user:password', 1, '/user/password', 'user/password/index', 'password', 2);

后端实现

1. 查询用户菜单

public List<Menu> getUserMenus(Long userId) {
    // SQL查询用户的权限菜单
    String sql = """
        SELECT DISTINCT p.* 
        FROM permissions p 
        JOIN role_permissions rp ON p.permission_id = rp.permission_id 
        JOIN user_roles ur ON rp.role_id = ur.role_id 
        WHERE ur.user_id = ? 
        AND p.perm_type = 1 
        AND p.status = 1 
        ORDER BY p.sort_order
    """;
    
    List<Permission> permissions = jdbcTemplate.query(sql, new Object[]{userId}, permissionRowMapper);
    return buildMenuTree(permissions);
}

// 构建菜单树
private List<Menu> buildMenuTree(List<Permission> permissions) {
    List<Menu> menus = new ArrayList<>();
    Map<Long, Menu> menuMap = new HashMap<>();
    
    // 转换为菜单对象
    for (Permission p : permissions) {
        Menu menu = new Menu();
        menu.setId(p.getPermissionId());
        menu.setParentId(p.getParentId());
        menu.setName(p.getPermName());
        menu.setPath(p.getPath());
        menu.setComponent(p.getComponent());
        menu.setIcon(p.getIcon());
        menuMap.put(menu.getId(), menu);
    }
    
    // 构建树形结构
    for (Menu menu : menuMap.values()) {
        if (menu.getParentId() == 0) {
            menus.add(menu);
        } else {
            Menu parentMenu = menuMap.get(menu.getParentId());
            if (parentMenu != null) {
                parentMenu.getChildren().add(menu);
            }
        }
    }
    
    return menus;
}

2. 菜单对象定义

public class Menu {
    private Long id;
    private Long parentId;
    private String name;
    private String path;
    private String component;
    private String icon;
    private List<Menu> children = new ArrayList<>();
    // getter和setter方法
}

前端实现

1. Vue路由配置

// 动态生成路由
function generateRoutes(menus) {
  return menus.map(menu => {
    const route = {
      path: menu.path,
      name: menu.name,
      component: loadComponent(menu.component),
      meta: {
        title: menu.name,
        icon: menu.icon
      }
    }
    
    if (menu.children && menu.children.length > 0) {
      route.children = generateRoutes(menu.children)
    }
    
    return route
  })
}

// 加载组件
function loadComponent(component) {
  if (component === 'Layout') {
    return Layout
  }
  return () => import(`@/views/${component}.vue`)
}

2. 菜单渲染组件

<!-- Menu.vue -->
<template>
  <el-menu :default-active="activeMenu" :collapse="isCollapse">
    <template v-for="menu in menus" :key="menu.id">
      <!-- 有子菜单 -->
      <el-sub-menu v-if="menu.children && menu.children.length" :index="menu.path">
        <template #title>
          <el-icon><component :is="menu.icon" /></el-icon>
          <span>{{ menu.name }}</span>
        </template>
        <menu-item v-for="child in menu.children" 
                   :key="child.id" 
                   :menu="child" />
      </el-sub-menu>
      <!-- 无子菜单 -->
      <el-menu-item v-else :index="menu.path">
        <el-icon><component :is="menu.icon" /></el-icon>
        <span>{{ menu.name }}</span>
      </el-menu-item>
    </template>
  </el-menu>
</template>

<script>
export default {
  name: 'Menu',
  props: {
    menus: {
      type: Array,
      required: true
    }
  },
  computed: {
    activeMenu() {
      return this.$route.path
    },
    isCollapse() {
      return this.$store.state.app.sidebar.collapsed
    }
  }
}
</script>

使用示例

1. 获取菜单数据

// 在Vuex中存储菜单数据
const state = {
  menus: []
}

const mutations = {
  SET_MENUS: (state, menus) => {
    state.menus = menus
  }
}

const actions = {
  // 获取用户菜单
  async getUserMenus({ commit }) {
    const response = await getUserMenus()
    commit('SET_MENUS', response.data)
    return response.data
  }
}

2. 应用菜单

// 在路由守卫中处理
router.beforeEach(async (to, from, next) => {
  if (hasToken) {
    if (!store.state.user.menus.length) {
      // 获取用户菜单
      const menus = await store.dispatch('getUserMenus')
      // 生成路由
      const routes = generateRoutes(menus)
      // 动态添加路由
      routes.forEach(route => {
        router.addRoute(route)
      })
      next({ ...to, replace: true })
    } else {
      next()
    }
  }
})

最佳实践建议

1. 性能优化

  • 使用Redis缓存菜单树
  • 按需加载组件
  • 合理使用菜单层级

2. 安全考虑

  • 前后端同时验证权限
  • 定期清理无效菜单
  • 记录菜单访问日志

3. 扩展功能

  • 支持菜单拖拽排序
  • 实现菜单搜索功能
  • 添加菜单收藏功能
  • 支持多主题切换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老大白菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

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

抵扣说明:

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

余额充值