权限方案与菜单路由
权限模型:RBAC + 自定义扩展
这个项目使用的是 RBAC(Role-Based Access Control)+ 自定义扩展 的权限方案,具体包含:
- 基础 RBAC 模型:用户 → 角色 → 权限
- 扩展层:增加了直接的用户权限控制
- 超级管理员机制:
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种路由类型:
M(Menu)- 菜单路由
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' }
}L(Link)- 外部链接
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>🎯 权限方案特点
优势:
- 多层次控制:权限码、角色、用户三层验证
- 超级管理员机制:
SuperAdmin角色和*权限码 - 动态路由:基于菜单数据动态生成路由
- 细粒度控制:支持按钮级别的权限控制
- 灵活扩展:支持自定义权限验证逻辑
创新点:
- 菜单驱动:后端控制前端路由结构
- 多类型路由:M/B/I/L 四种类型满足不同需求
- 权限码自动生成:从菜单结构自动提取权限码
- 组件自动加载:支持模块和插件的组件动态加载
这是一个非常完善的企业级权限方案,结合了 RBAC 的标准化和自定义扩展的灵活性,特别适合复杂的后台管理系统。
