RBAC 权限系统实战(三):细粒度的权限控制
本文主要讲解 RBAC 后台系统中的按钮级权限控制 在前两篇文章:RBAC 权限系统实战(一):页面级访问控制全解析、RBAC 权限系统实战(二):权限信息管理的设计 中,我们在后台系统里已经实现了权限控制,但从权限细粒度的角度看,我们只做了“页面级”权限 在权限细粒度中,一般有这三种权限粒度: 在某些业务场景下,我们希望用户能看到/进入页面,但不一定能操作所有功能。比如同一个列表页:A 只能“查看”,B 可以“新增/编辑”,C 还能“删除/导出” 因此本文主要实现操作级权限控制,也常称为“按钮级权限控制” 在第一篇权限文章中,我们在登录后,会请求用户信息接口,拿到用户的菜单路由数据再渲染访问 现在,还是基于这个接口返回的用户信息,我们要增加一个字段: 你可以看到,返回的权限码列表遵循一定的格式来确保语义清晰,我们约定,权限码的格式如下: 比如下面的菜单模块,关于新增、详情的权限码: 当然,不是说不遵守这个格式就不行,但我推荐这种格式。可以让我们在代码里更清楚地理解权限码含义,也方便后续维护 还要考虑一种情况:某个角色不管系统有多少权限都可以访问,比如超级管理员 在操作级权限设计中,在 Vue 框架下,有三种实现按钮级权限的方式:组件式、自定义指令、函数式 函数式写法最常见,把权限判断封装成工具函数或 这里我把逻辑写成一个 实际场景中,可配合 组件式很好理解:把“权限判断”封装成 Vue 组件,内部内容由权限码决定是否渲染。 这里用 在频繁使用的场景下,最好全局注册该组件: 全局注册组件时要补上 然后就可以直接使用 自定义指令也是一种很方便的实现方式,通过操作 通过 然后在 同样要补上全局指令的类型定义,在 然后就可以使用 在权限实战第二篇:RBAC 权限系统实战(二):权限信息管理的设计 中,实现了菜单、角色管理的基本管理操作,比如菜单 CRUD、角色绑定权限等操作 从细粒度来看,我们现在多做了一层操作级权限,关于这两个模块,要进行一点小改动 在菜单管理中,新增、编辑菜单等操作中,新加一个”操作“的类型,以支持添加操作级权限信息 注意这里要填写的表单信息,是根据菜单类型来展示不同的字段,比如“操作”类型,需要填写权限码 然后,菜单列表的数据是这样的: 在角色管理模块中,主要关注”分配权限“的操作,允许给角色分配操作级权限 系列专栏地址:GitHub 博客 | 掘金专栏 | 思否专栏 实战项目:vue-clean-admin 文章如有错误或需要改进之处,欢迎指正。前言
本文是《通俗易懂的中后台系统建设指南》系列的第十一篇文章,该系列旨在告诉你如何构建一个优秀的中后台管理系统。
RBAC 权限细粒度
权限码设计
permissionCodes,它是一个字符串数组,代表该用户拥有的操作权限我这里使用的是 ApiFox 来模拟的接口和数据,ApiFox 文档可以访问:vue-clean-admin ApiFox 文档,内有关于”获取用户信息接口“的文档介绍



superAdmin,对于这样的角色,我们做点特殊处理,比如用通配符 * 表示全部权限(如 *:*:*)。这时无需再做权限筛选,直接放行即可按钮级权限实战
函数式
hook 都可以。先看一个实现:import { useUserStore } from '@/store/modules/user';
import { PermissionCode } from '#/type';
import { storeToRefs } from 'pinia';
import { isEmpty } from '@/utils';
export const useAuth = () => {
const userStore = useUserStore();
const { getPermissionCodes } = storeToRefs(userStore);
//...
/**
* 判断是否有权限
* @param code 权限码,可以是单个权限码字符串,也可以是权限码数组
* @returns 是否有权限
*/
const hasPermission = (code: PermissionCode): boolean => {
// 如果是特殊通配符,直接放行
if (getPermissionCodes.value.includes('*:*:*')) return true;
// 空字符串、空数组情况,默认为无权限
if (isEmpty(code)) return false;
const codes = Array.isArray(code) ? code : [code];
// 只要满足其中一个权限码即可
return codes.some((c) => getPermissionCodes.value.includes(c));
};
return {
hasPermission,
};
};
在 use-auth.ts 找到实战代码
hook,重点关注 hasPermission 方法。它接收权限码参数 code,返回一个布尔值,表示是否有权限getPermissionCodes 表示当前用户拥有的权限码code 参数既可以是单个字符串,也可以是数组。因为用户可以同时拥有多个权限码(如 user:add、user:edit),所以类型定义如下:/**
* 权限码类型
*/
export type PermissionCode<T = string | string[]> = T;
v-if 来控制元素显隐:
组件式
AppAuth 组件示例:<script setup lang="ts">
import { PermissionCode } from '@/types/common';
import { computed } from 'vue';
import { useAuth } from '@/hooks/useAuth';
defineOptions({
name: 'AppAuth',
});
export interface AppAuthProps {
/**
* 权限码
*/
codes: PermissionCode;
}
const props = withDefaults(defineProps<AppAuthProps>(), {
codes: '',
});
const { hasPermission } = useAuth();
/**
* 是否有权限
*/
const hasAuth = computed(() => {
return hasPermission(props.codes);
});
</script>
<template>
<slot v-if="hasAuth" />
<slot v-else name="no-auth" />
</template>在 app-auth.vue 找到代码实现
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import AppAuth from './components/AppAuth.vue'
const app = createApp(App)
// 全局注册
app.component('AppAuth', AppAuth)
app.mount('#app')
TypeScript 的类型提示,我在 src/typings/app-components.d.ts 中添加了类型声明:export {};
declare module 'vue' {
export interface GlobalComponents {
//...
AppAuth: (typeof import('../components/common/app-auth/index'))['AppAuth'];
}
}
AppAuth 组件了:
当用户没有权限时,会渲染
no-auth 插槽的内容,可以在 no-auth 插槽中自定义展示内容。自定义指令
DOM 来实现元素显隐app.directive 方法来注册 v-auth// directives/auth.ts
import type { Directive } from 'vue';
import type { PermissionCode } from '@/types';
import { useAuth } from '@/hooks/useAuth';
export type AuthDirective = Directive<HTMLElement, PermissionCode>;
export const authDirective: AuthDirective = {
mounted(el, binding) {
const { hasPermission } = useAuth();
if (!hasPermission(binding.value)) {
el.remove();
}
},
updated(el, binding) {
const { hasPermission } = useAuth();
if (!hasPermission(binding.value)) {
el.remove();
}
},
};
在 directives/modules/auth.ts 找到代码实现
main.ts 中注册 v-auth 指令:// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { authDirective } from './directives/auth';
const app = createApp(App);
app.directive('auth', authDirective);
app.mount('#app');src/typings/directive.d.ts 中添加类型声明:import type { AuthDirective } from '@/directives/typing';
declare module 'vue' {
export interface GlobalDirectives {
vAuth: AuthDirective;
}
}
v-auth 指令来实现权限控制:
菜单管理、角色管理



了解更多
交流讨论