предисловие
В проекте некоторые функции будут связаны с важным управлением данными.Для обеспечения безопасности данных мы добавим разрешения в проект, чтобы ограничить операции каждого пользователя. Что касается внешнего интерфейса, то нам нужно сотрудничать с данными разрешений, предоставленными серверной частью, чтобы накладывать различные ограничения на страницу.
необходимость
Поскольку это бизнес-требование на работе, для меня в основном есть два места, где требуется контроль разрешений.
Первая — это боковая панель меню, которую нужно отображать и скрывать.
Второе — это кнопки, всплывающие окна и т. д. на странице.
Процесс
-
Как получить права пользователя?
Back-end (список разрешений, принадлежащих текущему пользователю) -> front-end (полученный через back-end интерфейс, здесь и далее мы называем список разрешений текущего пользователя permissionList)
-
Как ограничить переднюю часть?
В соответствии с требованиями продукта настройте точки разрешений в проекте, а затем с помощью списка разрешений узнайте, есть ли настроенные точки разрешений.
-
Тогда что?
Ушел.
Вот о чем я подумал, когда впервые получил этот запрос, что в нем такого сложного, просто получите PermissionList и судите по нему. Только позже я обнаружил, что настоящие потребности гораздо сложнее, чем я думал.
реальная проблема
В приведенных выше требованиях упоминалось, что мы в основном решаем две проблемы: отображение боковой панели меню и работа на странице.
Предположим, у нас есть такая настройка маршрутизации (далее приведен только пример):
import VueRouter from 'vue-router'
/* 注意:以下配置仅为部分配置,并且省去了 component 的配置 */
export const routes = [
{
path: '/',
name: 'Admin',
label: '首页'
},
{
path: '/user',
name: 'User',
label: '用户',
redirect: { name: 'UserList' },
children: [
{
path: 'list',
name: 'UserList',
label: '用户列表'
},
{
path: 'group',
name: 'UserGroup',
label: '用户组',
redirect: { name: 'UserGroupList' },
children: [
{
path: 'list',
name: 'UserGroupList',
label: '用户组列表'
},
{
path: 'config',
name: 'UserGroupConfig',
label: '用户组设置'
}
]
}
]
},
{
path: '/setting',
name: 'Setting',
label: '系统设置'
},
{
path: '/login',
name: 'Login',
label: '登录'
}
]
const router = new VueRouter({
routes
})
export default router
Первые два уровня маршрутизации будут отображаться на боковой панели, а третий уровень не будет отображаться на боковой панели.
Настройки разрешений для операций на странице не нужно рассматривать много других вещей.Мы в основном разбираем проблемы сайдбара и роутинга.Посредством анализа в основном выявляются следующие проблемы:
- Когда получать список разрешений и как хранить список разрешений
- Подмаршруты не должны отображаться, если у них вообще нет разрешений (например: пользователи не должны отображаться на боковой панели, когда ни список пользователей, ни группа пользователей не имеют разрешений)
- Когда перенаправленный маршрут по умолчанию не имеет разрешения, вы должны искать перенаправление с разрешением в дочерних элементах (например: маршрут пользователя перенаправляется на маршрут списка пользователей, если список пользователей не имеет разрешения, он должен быть перенаправлен на маршрут группы пользователей)
- Когда пользователь напрямую вводит неавторизованный URL-адрес, ему необходимо перейти на неавторизованную страницу или выполнить другие операции. (ограничения маршрутизации)
Ниже мы решаем вышеуказанные проблемы одну за другой.
Когда получать разрешения, где хранить и ограничения маршрутизации
я здесьrouterизbeforeEachПолученный список разрешений сохраняется вvuexсередина.
Причина в том, чтобы учесть ограничения на маршрутизацию и упростить использование списков разрешений в последующих проектах.Ниже приведен пример реализации:
Сначала мы добавляем конфигурацию разрешений на маршрутизатор:
// 以下只展示部分配置
{
path: '/user',
name: 'User',
label: '用户',
meta: {
permissions: ['U_1']
},
redirect: { name: 'UserList' },
children: [
{
path: 'list',
name: 'UserList',
label: '用户列表',
meta: {
permissions: ['U_1_1']
}
},
{
path: 'group',
name: 'UserGroup',
label: '用户组',
meta: {
permissions: ['U_1_2']
},
redirect: { name: 'UserGroupList' },
children: [
{
path: 'list',
name: 'UserGroupList',
label: '用户组列表',
meta: {
permissions: ['U_1_2_1']
}
},
{
path: 'config',
name: 'UserGroupConfig',
label: '用户组设置',
meta: {
permissions: ['U_1_2_2']
}
}
]
}
]
}
Вы можете видеть, что мы добавили права доступа к метаданным, чтобы упростить доступ кrouter.beforeEchРазрешения оцениваются в , а разрешения задаются в виде массива, поскольку страница может включать несколько разрешений.
Далее мы устанавливаемrouter.beforeEach :
// 引入项目的 vuex
import store from '@/store'
// 引入判断是否拥有权限的函数
import { includePermission } from '@/utils/permission'
router.beforeEach(async (to, from, next) => {
// 先判断是否为登录,登录了才能获取到权限,怎么判断登录就不写了
if (!isLogin) {
try {
// 这里获取 permissionList
await store.dispatch('getPermissionList')
// 这里判断当前页面是否有权限
const { permissions } = to.meta
if (permissions) {
const hasPermission = includePermission(permissions)
if (!hasPermission) next({ name: 'NoPermission' })
}
next()
}
} else {
next({ name: 'Login' })
}
})
Мы видим, что нам нужен метод для оценки разрешений, и getPermissionList в vuex выглядит следующим образом:
// @/store
export default {
state: {
permissionList: []
},
mutations: {
updatePermissionList: (state, payload) => {
state.permissionList = payload
}
},
actions: {
getPermissionList: async ({ state, commit }) => {
// 这里是为了防止重复获取
if (state.permissionList.length) return
// 发送请求方法省略
const list = await api.getPermissionList()
commit('updatePermissionList', list)
}
}
}
// @/utils/permission
import store from '@/store'
/**
* 判断是否拥有权限
* @param {Array<string>} permissions - 要判断的权限列表
*/
function includePermission (permissions = []) {
// 这里要判断的权限没有设置的话,就等于不需要权限,直接返回 true
if (!permissions.length) return true
const permissionList = store.state.permissionList
return !!permissions.find(permission => permissionList.includes(permission))
}
проблема перенаправления
Выше мы разобрались с базовой настройкой роутинга и как получить разрешения, и как ограничить роутинговые переходы, Следующее, с чем нам нужно разобраться, это проблема перенаправления.
Это может быть связано со структурой самого нашего проекта. Под боковой панелью нашего проекта есть подуровни, которые отображаются с помощью переключателя вкладок на следующем рисунке. В обычных условиях, когда вы нажимаете на управление лекарствами, страница быть перенаправлены на вкладку переключения страницы управления хранением, но когда входящее управление не имеет разрешения, оно должно быть перенаправлено непосредственно в исходящий интерфейс управления.
Таким образом, для достижения вышеуказанного эффекта мне нужно переписать перенаправление маршрутизатора, чтобы его можно было оценивать динамически (потому что я не знаю список разрешений текущего пользователя при настройке маршрута).
Затем я проверил документацию vue-router и обнаружил, что перенаправление может быть методом, позволяющим решить проблему перенаправления.
описание перенаправления в vue-router, по инструкции можем переписать редирект следующим образом:
// 我们需要引入判断权限方法
import { includePermission } from '@/utils/permission'
const children = [
{
path: 'list',
name: 'UserList',
label: '用户列表',
meta: {
permissions: ['U_1_1']
}
},
{
path: 'group',
name: 'UserGroup',
label: '用户组',
meta: {
permissions: ['U_1_2']
}
}
]
const routeDemo = {
path: '/user',
name: 'User',
label: '用户',
redirect: (to) => {
if (includePermission(children[0].meta.permissions)) return { name: children[0].name }
if (includePermission(children[1].meta.permissions)) return { name: children[1].name }
},
children
}
Хоть проблема и решаема, но писать так очень хлопотно, да и конфигурацию роутера нужно модифицировать, поэтому используем метод для генерации:
// @/utils/permission
/**
* 创建重定向函数
* @param {Object} redirect - 重定向对象
* @param {string} redirect.name - 重定向的组件名称
* @param {Array<any>} children - 子列表
*/
function createRedirectFn (redirect = {}, children = []) {
// 避免缓存太大,只保留 children 的 name 和 permissions
const permissionChildren = children.map(({ name = '', meta: { permissions = [] } = {} }) => ({ name, permissions }))
return function (to) {
// 这里一定不能在 return 的函数外面筛选,因为权限是异步获取的
const hasPermissionChildren = permissionChildren.filter(item => includePermission(item.permissions))
// 默认填写的重定向的 name
const defaultName = redirect.name || ''
// 如果默认重定向没有权限,则从 children 中选择第一个有权限的路由做重定向
const firstPermissionName = (hasPermissionChildren[0] || { name: '' }).name
// 判断是否需要修改默认的重定向
const saveDefaultName = !!hasPermissionChildren.find(item => item.name === defaultName && defaultName)
if (saveDefaultName) return { name: defaultName }
else return firstPermissionName ? { name: firstPermissionName } : redirect
}
}
Тогда мы можем переписать это как:
// 我们需要引入判断权限方法
import { includePermission, createRedirectFn } from '@/utils/permission'
const children = [
{
path: 'list',
name: 'UserList',
label: '用户列表',
meta: {
permissions: ['U_1_1']
}
},
{
path: 'group',
name: 'UserGroup',
label: '用户组',
meta: {
permissions: ['U_1_2']
}
}
]
const routeDemo = {
path: '/user',
name: 'User',
label: '用户',
redirect: createRedirectFn({ name: 'UserList' }, children),
children
}
Это немного более лаконично, но мне все еще нужно изменить один маршрут за другим, поэтому я написал еще один метод для рекурсивной настройки маршрутизаторов и переписывания их перенаправлений:
// @/utils/permission
/**
* 创建有权限的路由配置(多级)
* @param {Object} config - 路由配置对象
* @param {Object} config.redirect - 必须是 children 中的一个,并且使用 name
*/
function createPermissionRouter ({ redirect, children = [], ...others }) {
const needRecursion = !!children.length
if (needRecursion) {
return {
...others,
redirect: createRedirectFn(redirect, children),
children: children.map(item => createPermissionRouter(item))
}
} else {
return {
...others,
redirect
}
}
}
Таким образом, нам нужно только добавить такой уровень функций в конфигурацию самого внешнего маршрутизатора:
import { createPermissionRouter } from '@/utils/permission'
const routesConfig = [
{
path: '/user',
name: 'User',
label: '用户',
meta: {
permissions: ['U_1']
},
redirect: { name: 'UserList' },
children: [
{
path: 'list',
name: 'UserList',
label: '用户列表',
meta: {
permissions: ['U_1_1']
}
},
{
path: 'group',
name: 'UserGroup',
label: '用户组',
meta: {
permissions: ['U_1_2']
},
redirect: { name: 'UserGroupList' },
children: [
{
path: 'list',
name: 'UserGroupList',
label: '用户组列表',
meta: {
permissions: ['U_1_2_1']
}
},
{
path: 'config',
name: 'UserGroupConfig',
label: '用户组设置',
meta: {
permissions: ['U_1_2_2']
}
}
]
}
]
}
]
export const routes = routesConfig.map(item => createPermissionRouter(item))
const router = new VueRouter({
routes
})
export default router
Конечно, в таком написании есть еще одно преимущество: вам не нужно устанавливать редирект, который будет автоматически перенаправлять на первый авторизованный маршрут дочерних элементов.
Проблема с отображением боковой панели
Наш проект использует конфигурацию маршрута для генерации сайдбара.Конечно, будут добавлены некоторые другие параметры для отображения уровня отображения и других вопросов.Я не буду писать здесь конкретный код.Как решить проблему, которая возникает у детей в боковая панель не отображается без разрешения.проблема.
Моя идея состоит в том, чтобы вместе обновить конфигурацию маршрутизации до vuex, а затем прочитать конфигурацию боковой панели из конфигурации в vuex.
Так как это место требует много модификаций и бизнеса, я не буду выносить код, вы можете поэкспериментировать сами.
Способы облегчить командам развертывание точек доступа
Мы решили большую часть вышеперечисленных проблем с разрешениями, поэтому остается еще много развертываний точек разрешений, связанных с бизнес-логикой, поэтому для того, чтобы другие люди в команде могли элегантно и просто развертывать точки разрешений на каждой странице, я предоставляю следующее в проект Способы развертывания разрешений:
- по команде
v-permissionустановить прямо на шаблон
<div v-permission="['U_1']"></div>
- через глобальный метод
this.$permissionСуждение, потому что некоторых разрешений нет в шаблоне
{
hasPermission () {
// 通过方法 $permission 判断是否拥有权限
return this.$permission(['U_1_1', 'U_1_2'])
}
}
Обратите внимание, что для$permissionВозвращаемое значение метода можно контролировать, и о нем нужно судить поthis.$storeЧтобы судить, следующий код реализации:
// @/utils/permission
/**
* 判断是否拥有权限
* @param {Array<string|number>} permissions - 要判断的权限列表
* @param {Object} permissionList - 传入 store 中的权限列表以实现数据可监测
*/
function includePermissionWithStore (permissions = [], permissionList = []) {
if (!permissions.length) return true
return !!permissions.find(permission => permissionList.includes(permission))
}
import { includePermissionWithStore } from '@/utils/permission'
export default {
install (Vue, options) {
Vue.prototype.$permission = function (permissions) {
const permissionList = this.$store.state.permissionList
return includePermissionWithStore(permissions, permissionList)
}
}
}
Ниже приведен код реализации директивы (чтобы не конфликтовать с v-if, отображение элемента управления скрывается добавлением/удалением className):
// @/directive/permission
import { includePermission } from '@/utils/permission'
const permissionHandle = (el, binding) => {
const permissions = binding.value
if (!includePermission(permissions)) {
el.classList.add('hide')
} else {
el.classList.remove('hide')
}
}
export default {
inserted: permissionHandle,
update: permissionHandle
}
Суммировать
В ответ на предыдущий вопрос, есть следующее резюме:
-
Когда получать список разрешений и как хранить список разрешений
router.beforeEach получает его и сохраняет в vuex.
-
Подмаршруты не должны отображаться, если у них вообще нет разрешений (например: пользователи не должны отображаться на боковой панели, если ни список пользователей, ни пользовательские настройки не имеют разрешений)
Сохраняя конфигурацию маршрутизации в vuex, создавайте настройки боковой панели и изменяйте конфигурацию в vuex после получения разрешений на управление отображением и скрытием.
-
Когда перенаправленный маршрут по умолчанию не имеет разрешения, вы должны искать перенаправление с разрешением в дочерних элементах (например: маршрут пользователя перенаправляется на маршрут списка пользователей, если список пользователей не имеет разрешения, он должен быть перенаправлен на маршрут группы пользователей)
пройти через
vue-routerсерединаredirectУстановить какFunctionреализовать -
Когда пользователь напрямую вводит неавторизованный URL-адрес, ему необходимо перейти на неавторизованную страницу или выполнить другие операции. (ограничения маршрутизации)
Установите разрешения в meta и оцените разрешения в router.beforeEach.
Выше приведено мое общее решение и реализация кода для этого требования к разрешению. Возможно, оно не идеально, но я все же надеюсь, что оно может вам помочь ^_^