后台管理系统RBAC权限模型介绍

233 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

铁铁们可能对于RBAC权限设计很陌生,接下来给大家简单介绍一下吧

image.png

三个关键点: 员工, 角色, 权限

  1. 给员工分配角色
  2. 给角色分配权限

进入一家公司, 入职, 人事将你录入系统 => 分配你的角色 (前端开发工程师)

同角色有着相同的权限, 操作角色权限的同时, 所有该角色的用户对应权限, 就会同步更新

给员工分配角色

image.png

用户和角色是**1对多**的关系,即一个用户可以拥有多个角色,比如公司的董事长可以拥有总经理和系统管理员一样的角色

点击这个员工,获取到员工的角色,给员工分配角色

实现步骤

新建分配角色弹框

首先,新建分配角色窗体 employees/components/assign-role.vue

<template>
  <el-dialog class="assign-role" title="分配角色" :visible="showRoleDialog">
    <!-- 这里准备复选框 -->
    ...
​
    <template #footer>
      <el-button type="primary" size="small">确定</el-button>
      <el-button size="small">取消</el-button>
    </template>
  </el-dialog>
</template><script>
export default {
  props: {
    showRoleDialog: {
      type: Boolean,
      default: false
    },
    // 用户的id 用来查询当前用户的角色信息
    userId: {
      type: String,
      default: null
    }
  }
}
</script>

弹层的显示和关闭

点击角色按钮显示弹层

注册组件

import AssignRole from './components/assign-role'components: {
  AddEmployee,
  AssignRole
},
  
<assign-role :show-role-dialog.sync="showRoleDialog" :user-id="userId" />

点击角色按钮, 记录id, 显示弹层

<el-button type="text" size="small" @click="editRole(row.id)">角色</el-button>
​
editRole(id) {
  this.userId = id
  this.showRoleDialog = true
}

弹层的关闭

<template>
  <el-dialog class="assign-role" title="分配角色" :visible="showRoleDialog" @close="closeDialog">
    <!-- el-checkbox-group选中的是 当前用户所拥有的角色  需要绑定 当前用户拥有的角色-->
    <el-checkbox-group>
      <!-- 选项 -->
    </el-checkbox-group>
​
    <template #footer>
      <div style="text-align: right">
        <el-button type="primary">确定</el-button>
        <el-button @click="closeDialog">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template><script>
export default {
  props: {
    showRoleDialog: {
      type: Boolean,
      default: false
    },
    // 用户的id 用来查询当前用户的角色信息
    userId: {
      type: String,
      default: null
    }
  },
  methods: {
    closeDialog() {
      this.$emit('update:showRoleDialog', false)
    }
  }
}
</script>

获取角色列表

<el-checkbox-group v-model="roleIds">
  <el-checkbox label="110">管理员</el-checkbox>
  <el-checkbox label="113">开发者</el-checkbox>
  <el-checkbox label="115">人事</el-checkbox>
</el-checkbox-group>

发送请求获取角色列表

import { reqGetRoleList } from '@/api/setting'
export default {
  props: {
    showRoleDialog: {
      type: Boolean,
      default: false
    },
    // 用户的id 用来查询当前用户的角色信息
    userId: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      list: [],
      roleIds: []
    }
  },
  created() {
    this.getRoleList()
  },
  methods: {
    closeDialog() {
      this.$emit('update:showRoleDialog', false)
    },
    async getRoleList() {
      const { data } = await reqGetRoleList(1, 100)
      this.list = data.rows
    }
  }
}

渲染数据

<el-checkbox-group v-model="roleIds">
  <el-checkbox v-for="item in list" :key="item.id" :label="item.id">
    {{ item.name }}
  </el-checkbox>
</el-checkbox-group>

微调样式

<style lang="scss" scoped>
.assign-role {
  ::v-deep {
    .el-checkbox {
      font-size: 30px;
    }
  }
}
</style>

然后根据传递过来的id,获取用户的角色信息进行回显,重点一点点的就是在open这个方法中实现,弹出框显示发送ajax,然后给员工分配角色

权限点管理页面开发

目标: 完成权限点页面的开发和管理 => 为了后面做准备

  1. 便于分配权限, 只有有权限了才能分配
  2. 只有分配好了权限, 有对应的权限规则, 才能控制路由(模块访问权), 才能控制按钮的显示(操作权)

新建权限点管理页面

人已经有了角色,只要给角色分配权限, 人就有了权限

那么权限是什么

在企业服务中,权限一般分割为 页面访问权限按钮操作权限API访问权限

API权限多见于在后端进行拦截,所以我们需要做**页面访问按钮操作授权**

由此,我们可以根据业务需求, 设计权限管理页面

在就是前端的路由了

前端权限-页面访问权(路由)

目标: 在当前项目应用用户的页面访问权限

权限受控的思路 - addRoutes

到了最关键的环节,我们设置的权限如何应用?

在上面的几个小节中,我们已经把给用户分配了角色, 给角色分配了权限,

那么在用户登录获取资料的时候,会自动查出该用户拥有哪些权限,这个权限需要和我们的菜单还有路由有效结合起来

而动态路由表其实就是根据用户的实际权限来访问的,接下来我们操作一下

image.png

在权限管理页面中,我们设置了一个标识, 这个标识可以和我们的路由模块进行关联,

如果用户拥有这个标识,那么用户就可以 拥有这个路由模块,如果没有这个标识,就不能访问路由模块

addRoutes 的基本使用

拿到权限信息之后, 应该根据权限信息, 从动态路由模块中筛选出, 需要追加的路由,

        // 追加到routes规则中, addRoutes
        router.addRoutes(asyncRoutes)
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
        return
      }

最后将数据放入vuex中

新建Vuex路由权限模块

可以在vuex中新增一个permission模块, 专门维护管理, 所有的路由 routes 数组 (响应式, 所有地方都能用)

this.$router.options.routes 最初配置的路由信息表 如果有addRoutes动态新增的情况, vuex管理, 拿vuex中routes

在任何的组件中, 都可以非常方便的将来拿到 vuex 中存的 routes, 而不是去找 router.options.routes (拿的是默认配置)

src/store/modules/permission.js

import { constantRoutes } from '@/router'const state = {
  // 路由表, 标记当前用户所拥有的所有路由
  routes: constantRoutes // 默认静态路由表
}
const mutations = {
  // otherRoutes登录成功后, 需要添加的新路由
  setRoutes(state, otherRoutes) {
    // 静态路由基础上, 累加其他权限路由
    state.routes = [...constantRoutes, ...otherRoutes]
  }
}
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
​

在Vuex管理模块中引入permisson模块

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import permission from './modules/permission'Vue.use(Vuex)
​
const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    permission
  },
  getters
})
​
export default store

Vuex-action筛选权限路由

将来登录成功时, 个人信息中会有 roles 的 menus 信息, 要基于menus来过滤出我们需要给用户 add 的路由,

但是**menus**中的标识又该怎么和路由对应呢?

可以将路由模块**name**属性命名和权限标识一致,这样只要标识能对上,就说明用户拥有了该权限

这一步,在我们命名路由的时候已经操作过了

接下来, vuex的 permission 中提供一个action,进行关联

import { asyncRoutes, constantRoutes } from '@/router'const actions = {
  // 筛选路由权限
  filterRoutes(context, menus) {
    const otherRoutes = asyncRoutes.filter(item => {
      // 如果路由模块的首页name, 在menus数组中包含, 就是这个模块开放
      if (menus.includes(item.children[0].name)) {
        return true
      } else {
        return false
      }
    })
    context.commit('setRoutes', otherRoutes)
    return otherRoutes
  }
}

调用Action管理路由渲染菜单

在 permission 拦截的位置,调用关联action, 获取新增routes,并且addRoutes

...
if (!store.state.user.userInfo.userId) {
  // 调用获取信息的action
  const { roles } = await store.dispatch('user/getUserInfo')
  // 调用action同步到vuex中
  const otherRoutes = await store.dispatch('permission/filterRoutes', roles.menus)
  // 动态新增路由
  router.addRoutes(otherRoutes)
  next({
    ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
    replace: true // 重进一次, 不保留重复历史
  })
  return
}
...

在**src/store/getters.js**配置导出routes

const getters = {
  ...
  routes: state => state.permission.routes // 导出当前的路由
}
export default getters

在左侧菜单 layout/components/Sidebar/index.vue 组件中, 引入routes, 使用vuex的routes动态渲染

computed: {
  ...mapGetters([    'sidebar',    'routes'  ])
}

处理刷新 404 的问题

页面刷新的时候,本来应该拥有权限的页面出现了404,这是因为404的匹配权限放在了静态路由中 (静态路由的404要删除)

我们需要将404放置到动态路由的最后,404不放到最后就会出现刷新就出现404

src/permission.js

const otherRoutes = await store.dispatch('permission/filterRoutes', menus)
router.addRoutes([
  ...otherRoutes,
  { path: '*', redirect: '/404', hidden: true }
])

退出时重置路由

退出时, 需要将路由权限重置 (恢复默认), 将来登录后, 再次追加

我们的**router/index.js**文件,发现一个重置路由方法

// 重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}

这个方法就是将路由重新实例化,相当于换了一个新的路由,之前**加的路由**就不存在了,需要在登出的时候, 调用一下即可

store/modules/user.js

import { resetRouter } from '@/router'

// 退出的action操作
logout(context) {
  // 1. 移除vuex个人信息
  context.commit('removeUserInfo')
  // 2. 移除token信息
  context.commit('removeToken')
  // 3. 重置路由
  resetRouter()
  // 4. 重置 vuex 中的路由信息
  context.commit('permission/setRoutes', [], { root: true })
},