权限管理是每个后台管理系统都需要面对的问题。细粒度动态权限管理更是一个不太好啃但是必须解决的硬需求。本文记录了一种基于角色的动态权限管理方法,实现方法不是很优雅,如果以后发现了更优雅的方法再来更新。该方法使用了vuex 作为辅助,所以如果还没有接触vuex 的请移步:Vuex: 实现同级组件的简单通信
接下来主要从权限设置(设置角色权限)和权限限制(限制没有权限的人访问不到相应的内容)以及团队workflow三个方面展开讨论。
因为是基于角色的权限管理,所以权限设置页面又分为三个子页:菜单管理、用户管理、权限设置。
1. 菜单管理
菜单管理中所展示的是所有的可用后端接口,这个功能通常是面向开发团队中的测试或产品来提供信息录入操作。尤其是菜单URL,在将产品交付到甲方手中以后是不能让他们进行修改的,因为这个URL 是前端和后端提前商量好的,在系统运行过程中不能随意修改。
2. 用户管理
用户管理模块所提供的功能可以直接面向甲方,在这里他们可以自行创建所需要的登录账号并将每个账号加入不同的权限组(角色)来控制他们的访问和操作权限(实际上就是可以访问不同的后端接口)。
3. 权限管理
权限管理中可以新增权限组(也就是我们所说的角色了),并为每个权限组提供不同的访问权限,同时可以对属于该权限组的成员进行管理。
ok,到这里,我们就完成了对所有角色和用户的权限设置。此时,后端开发人员实际上已经对不同的用户行为进行了相应的限制。不过前端还没有进行任何工作,如果等操作人员点击按钮后才告诉他没有权限,这个交互就有点不太友好。所以我们在这里对前端页面也进行同等粒度的限制,用户不能操作的功能我们就直接让相应的按钮或者文本框消失或置灰,也可以温馨地提示用户“没有操作权限”。那么要怎样做呢?
1. 权限管理对象
假设后端有60 个操作数据库或其他任何东西的接口,因为要做到细粒度的控制,所以我们要对后端的60 个接口都进行处理,判断当前用户是否有权限访问其中的每一个接口,所以我们需要存储一个包含60 个变量的对象来限制当前用户能看到的网页内容。那么这60 个对象的取值如何设置呢?
// 接口访问权限对应的变量
var varMap = {
userAll: false,
userWholeInfo: false,
userBasicInfo: false,
userAddAndUpdate: false
// 省略很多变量.......
}
2. 权限URL 映射对象
这60 个变量的值需要通过跟用户可访问的URL 名对比之后来进行设置的,所以我们还需要一个对象来存储变量和URL 的对应关系。这样的话,当我们知道用户可以访问哪个URL 之后,我们就可以把这个URL 对应的变量设为TURE. 看到这里我们就知道为什么菜单管理里面不能让使用者随意修改菜单URL,因为这里的URL 是由前后端提前同步好的。
// 变量与接口名对应关系
const URL_MAP = {
// 老人管理
userAll: DOMAIN + 'admin/laoren/all',
userWholeInfo: DOMAIN + 'admin/laoren/info',
userBasicInfo: DOMAIN + 'admin/laoren/info/lr_id/info',
userAddAndUpdate: DOMAIN + 'admin/laoren/add/update'
// 依然省略很多......
}
3. 登录时设置当前用户权限
权限管理对象和权限URL 映射对象都有了。然后我们需要在用户登录的时候对他的权限进行限制(记得每次登陆时先清空后添加,否则刷新可能会出bug)。首先来看下登录的瞬间我们能从后端获取到什么数据,当然这里的数据只要前后端商量好就行,以什么结构书写都是可以的。
此时,我们拿到了一个当前用户能够访问的接口列表。接下来的操作就很清晰了:遍历这个权限访问数组,更新上面提到的存储访问权限变量的对象。
// 登录时候获取可以并根据可以访问的接口更改限制前端页面的变量
var SetAccessRule = function (urlList, urlMap, varMap) {
for (let item in urlList) {
SetMapRelationTrue(urlList[item], urlMap, varMap)
}
}
var SetMapRelationTrue = function (url, urlMap, varMap) {
// 所有的url都改为小写
for (let item in urlMap) {
if (url == urlMap[item]) {
varMap[item] = true
}
}
}
4. 找到你的按钮们
权限对象内的所有变量已经就位,接下来就是很细碎,比较花时间的工作了。去所有的页面上找到访问不同后端接口的按钮,根据相应的变量来控制他们是否显示或是否置灰。
5. Vuex 在哪里
因为所有页面中都需要访问到这个权限控制的对象,所以将它抽象出来,放到Vuex 中,作为一个全局的状态。在登录的瞬间也是使用Vuex 中的mutations 方法来对这个对象中的变量值进行设置。另外,我们其实可以将权限管理这一块整个抽象出来单独放到一个api.js 文件中:
/*
============================
@Author: shaoDong
@Version: 1.0
@DateTime: 2018-04-18 10:47:33
@Description: 权限访问控制
============================
*/
const DOMAIN = '/'
// 变量与接口名对应关系
const URL_MAP = {
userAll: DOMAIN + 'admin/laoren/all',
userWholeInfo: DOMAIN + 'admin/laoren/info'
// ......
}
// 接口访问权限对应的变量
var varMap = {
userAll: false,
userWholeInfo: false
// ......
}
// 登录时候获取可以并根据可以访问的接口更改限制前端页面的变量
var SetAccessRule = function (urlList, urlMap, varMap) {
for (let item in urlList) {
SetMapRelationTrue(urlList[item], urlMap, varMap)
}
}
var SetMapRelationTrue = function (url, urlMap, varMap) {
// 所有的url都改为小写
for (let item in urlMap) {
if (url == urlMap[item]) {
varMap[item] = true
}
}
}
export { DOMAIN, URL_MAP, varMap, SetAccessRule }
import Vue from 'vue'
import Vuex from 'vuex'
import * as api from '../api/api'
Vue.use(Vuex)
// initial state
// 在这里添加需要全局维护的状态
const state = {
// 权限限制相关
urlList: null,
varMap: api.varMap,
URL_MAP: api.URL_MAP
}
// getters 可根据需要使用
const getters = {
}
// actions 跟 mutations 作用相同,不过是异步操作
const actions = {
}
// mutations 修改共享状态 同步操作
// 在这里添加更改全局状态的方法
const mutations = {
SetAccessRule (state, urlList) {
api.SetAccessRule(urlList, state.URL_MAP, state.varMap)
},
ClearAccessRule (state) {
for (let index in state.varMap) {
state.varMap[index] = false
}
}
}
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
权限管理相关工作单靠开发人员是无法解决的,因为权限管理的需求最终是从甲方获取,所以在实际工作中完成权限管理部分的内容至少需要开发和产品的共同努力。谈到合作,就要说说工作流的问题了,到底怎么样做才能最大程度地提高合作的效率,也顺便说几句个人的想法吧。