Skip to content

权限方案与菜单路由

权限模型:RBAC + 自定义扩展

这个项目使用的是 RBAC(Role-Based Access Control)+ 自定义扩展 的权限方案,具体包含:

  1. 基础 RBAC 模型:用户 → 角色 → 权限
  2. 扩展层:增加了直接的用户权限控制
  3. 超级管理员机制SuperAdmin 角色拥有所有权限

🏗️ 三层权限架构

1. 权限码控制(Auth) - 最细粒度

typescript
// 权限码格式:模块:操作
v-auth="['user:add', 'user:edit', 'menu:delete']"

实现机制

typescript
export default function hasAuth(value: string | string[]): boolean {
  const auths = useUserStore().getPermissions() // 获取用户权限码数组
  
  if (auths[0] === '*') return true // 超级权限
  
  const values = Array.isArray(value) ? value : [value]
  return hasIncludesByArray(auths, values) // 检查是否包含所需权限
}

权限码生成逻辑

typescript
// 从菜单结构中递归提取权限码
const codes: string[] = recursionGetKey(getMenu(), 'name')
// 如果是超级管理员,添加通配符权限
getRoles().includes('SuperAdmin') && codes.unshift('*')
setPermissions(codes)

2. 角色控制(Role) - 中等粒度

typescript
v-role="['admin', 'manager', 'operator']"

实现机制

typescript
export default function hasRole(value: string | string[]): boolean {
  const roles = useUserStore().getRoles() // 获取用户角色数组
  
  if (roles.includes('SuperAdmin')) return true // 超级管理员通行证
  
  const values = Array.isArray(value) ? value : [value]
  return hasIncludesByArray(roles, values)
}

3. 用户控制(User) - 最高粒度

typescript
v-user="['admin', 'root', 'system']"

实现机制

typescript
export default function hasUser(value: string | string[]): boolean {
  const username = useUserStore().getUserInfo().username
  const values = Array.isArray(value) ? value : [value]
  return values.includes(username) // 直接检查用户名
}

🛣️ 菜单驱动路由系统

数据流程

后端菜单数据 → 前端路由转换 → 权限验证 → 动态注册

菜单数据结构

typescript
interface MenuVo {
  id?: number
  parent_id?: number
  name?: string           // 权限码/路由名称
  path?: string          // 路由路径
  component?: string     // 组件路径
  meta?: {
    type?: 'M' | 'B' | 'I' | 'L'  // 菜单类型
    title?: string       // 菜单标题
    icon?: string        // 图标
    auth?: string[]      // 权限码
    role?: string[]      // 角色
    user?: string[]      // 用户
    cache?: boolean      // 是否缓存
    hidden?: boolean     // 是否隐藏
  }
  children?: MenuVo[]    // 子菜单
}

4种路由类型

typescript
// 普通页面路由
{
  path: '/user/list',
  name: 'user:list',
  component: () => import('@/modules/base/views/user/list.vue'),
  meta: { type: 'M', title: '用户列表' }
}

B(Button)- 按钮权限

typescript
// 不生成路由,仅用于权限控制
{
  name: 'user:add',
  meta: { type: 'B', title: '添加用户' }
}

I(Iframe)- 内嵌页面

typescript
// 自动转换为iframe路由
{
  path: '/MineIframe/external-system',
  component: () => import('@/layouts/components/iframe/index.tsx'),
  meta: { type: 'I', url: 'https://external.com' }
}
typescript
// 直接跳转外部链接
{
  meta: { type: 'L', url: 'https://docs.mineadmin.com' }
}

菜单转路由核心实现

typescript
function menuToRoutes(routerMap: any[]) {
  const accessedRouters: any = []
  
  routerMap.forEach((item: any) => {
    // 跳过按钮类型
    if (item.meta?.type !== 'B') {
      
      // Iframe类型特殊处理
      if (item.meta.type === 'I') {
        item.path = `/MineIframe/${item.name}`
        item.component = () => import('@/layouts/components/iframe/index.tsx')
      }
      
      // 动态组件加载
      let component = null
      if (item.component && item.meta?.type !== 'I') {
        // 优先从模块中查找
        if (moduleViews[`../../modules/${item.component}.vue`]) {
          component = moduleViews[`../../modules/${item.component}.vue`]
        }
        // 其次从插件中查找
        else if (pluginViews[`../../plugins/${item.component}.vue`]) {
          component = pluginViews[`../../plugins/${item.component}.vue`]
        }
      }
      
      const route = {
        path: item.path,
        name: item.name,
        meta: item.meta,
        children: item.children ? menuToRoutes(item.children) : null,
        component: component
      }
      
      accessedRouters.push(route)
    }
  })
  
  return accessedRouters
}

🔒 权限验证流程

1. 登录时权限初始化

typescript
async function requestUserInfo(): Promise<void> {
  // 1. 获取用户信息
  const { data } = await getInfo()
  setUserInfo(data)
  
  // 2. 获取菜单和角色
  await refreshMenu()
  await refreshRole()
  
  // 3. 初始化路由
  await routeStore.initRoutes(router, getMenu())
  
  // 4. 生成权限码数组
  const codes: string[] = recursionGetKey(getMenu(), 'name')
  getRoles().includes('SuperAdmin') && codes.unshift('*')
  setPermissions(codes)
}

2. 路由级权限验证

typescript
router.afterEach(async (to) => {
  // 权限码验证
  if (!isEmpty(to.meta.auth) && !hasAuth(to.meta.auth as string[])) {
    await router.push({ path: '/403' })
    return
  }
  
  // 角色验证
  if (!isEmpty(to.meta.role) && !hasRole(to.meta.role as string[])) {
    await router.push({ path: '/403' })
    return
  }
  
  // 用户验证
  if (!isEmpty(to.meta.user) && !hasUser(to.meta.user as string[])) {
    await router.push({ path: '/403' })
    return
  }
})

3. 组件级权限验证

vue
<!-- 指令形式 -->
<el-button v-auth="['user:add']">添加用户</el-button>
<el-button v-role="['admin']">管理员功能</el-button>
<el-button v-user="['root']">超级用户功能</el-button>

<!-- 函数形式 -->
<el-button v-if="hasAuth(['user:edit'])">编辑</el-button>

🎯 权限方案特点

优势

  1. 多层次控制:权限码、角色、用户三层验证
  2. 超级管理员机制SuperAdmin 角色和 * 权限码
  3. 动态路由:基于菜单数据动态生成路由
  4. 细粒度控制:支持按钮级别的权限控制
  5. 灵活扩展:支持自定义权限验证逻辑

创新点

  1. 菜单驱动:后端控制前端路由结构
  2. 多类型路由:M/B/I/L 四种类型满足不同需求
  3. 权限码自动生成:从菜单结构自动提取权限码
  4. 组件自动加载:支持模块和插件的组件动态加载

这是一个非常完善的企业级权限方案,结合了 RBAC 的标准化和自定义扩展的灵活性,特别适合复杂的后台管理系统。