Сцены
В начале года я разработал систему управления мидл- и бэк-эндом, а функциями были задействованы разные отделы (продукты, клиентский сервис, маркетинг и т.д.) В начальной версии я и бэк-энд использовали схема разрешений серии цветочных штанов, касающаяся руки, которая была очень хороша на ранней стадии., но постепенно, по мере увеличения функций и усложнения бизнеса, это становится немного сложно, потому что наши разрешения слишком динамичны.
- Ряд рукопашных планов полномочий имеет относительно четкое разделение полномочий, а должностные обязанности отделов нашей компании иногда расплывчаты.
- Серверная часть использует схему разрешений RBAC.Чтобы выполнить требования пункта 1, роли очень тонко разделены, и роли иногда часто меняются, что приводит к необходимости ручного обслуживания каждого интерфейса.
Чтобы решить две вышеупомянутые болевые точки, я немного изменил исходное решение.
- Фронтенд больше не управляет разрешениями с ролями, а более мелкими операциями (интерфейсами), то есть фронтенду наплевать на роли
- Маршрутизация по-прежнему поддерживается внешним интерфейсом (наш сервер очень неохотно поддерживает вещи, которые не имеют к ним никакого отношения), но вместо этого маршрутизация разрешений фильтруется через список операций.
- Используйте единый метод (простой в обслуживании) для управления локальными разрешениями страницы, больше не используйте метод пользовательских команд, а используйте функциональные компоненты, потому что использование пользовательских команд имеет дополнительные накладные расходы (вставка, а затем удаление)
Сотрудничество бэкенда:
- Предоставляет интерфейс для получения списка текущих действий пользователя.
- В список операций необходимо добавить уникальный идентификатор (код операции) для внешнего интерфейса, который не изменился.
- Список действий нужно увеличить на один
routerNameПоля для визуального редактирования разрешений
Есть некоторые предостережения:
- Например, страница списка A с разрешением, и этот интерфейс списка используется страницей разрешения B, теперь вы настраиваете разрешение так, чтобы определенный пользователь не имел разрешения страницы A, но мог использовать страницу B, если ваше первоначальное намерение - использовать все функции страницы B, тогда будут проблемы, поэтому старайтесь не использовать интерфейс разрешений между страницами, вам нужно различать, какие данные нужно получить через интерфейс словаря или через интерфейс разрешений
- Некоторые люди могут быть запутаны, безопасно ли разрешение на обслуживание интерфейса? Это определенно небезопасно. Безопасность в основном контролируется серверной частью. Серверная часть хорошо справляется с управлением данными и разрешениями интерфейса, а интерфейс выполняет контроль разрешений. Я думаю, что это в основном для интерактивного опыта. . Почему ты хочешь, чтобы я видел это дерьмо без разрешения?
- Прежде чем использовать этот метод, необходимо уточнить, действительно ли текущий сценарий требует этого, ведь когда проект относительно большой и интерфейсов много, между вами и опкодом идет затяжная война.
выполнить
Пример списка действий
Возьмем в качестве примера интерфейс в стиле Restful.
const operations = [
{
url: '/xxx',
type: 'get',
name: '查询xxx',
routeName: 'route1', // 接口对应的路由
opcode: 'XXX_GET' // 操作码,不变的
},
{
url: '/xxx',
type: 'post',
name: '新增xxx',
routeName: 'route1',
opcode: 'XXX_POST'
},
// ......
]
изменения маршрутизации
в путиmetaДобавьте поле конфигурации, напримерrequireOps, значение может бытьStringилиArray, который представляет необходимые коды операций для отображения на текущей странице маршрутизации,ArrayЭтот тип предназначен для ситуации, когда необходимо отобразить страницу маршрутизации, когда одновременно имеется несколько разрешений на выполнение операций. Если значение отличается от этих двух, это считается отсутствием контроля разрешений, и любой пользователь может получить доступ.
Поскольку меню необходимо динамически генерировать в соответствии с отфильтрованной маршрутизацией разрешений, необходимо добавить несколько полей в параметры маршрутизации, чтобы решить проблему с отображением.hiddenприоритет больше, чемvisible
-
hidden, когда значение равно true, маршрут, включая подмаршруты, не будет отображаться в меню. -
visible, когда значение равно false, маршрут не отображается, но отображается подмаршрут
const permissionRoutes = [
{
// visible: false,
// hidden: true,
path: '/xxx',
name: 'route1',
meta: {
title: '路由1',
requireOps: 'XXX_GET'
},
// ...
}
]
Поскольку маршрутизация поддерживается во внешнем интерфейсе, приведенная выше конфигурация может быть жестко запрограммирована. лучше.
Фильтрация маршрутизации разрешений
Сначала стандартизируйте маршрутизацию разрешений и сохраните копию, которая может понадобиться для визуализации.
const routeMap = (routes, cb) => routes.map(route => {
if (route.children && route.children.length > 0) {
route.children = routeMap(route.children, cb)
}
return cb(route)
})
const hasRequireOps = ops => Array.isArray(ops) || typeof ops === 'string'
const normalizeRequireOps = ops => hasRequireOps(ops)
? [].concat(...[ops])
: null
const normalizeRouteMeta = route => {
const meta = route.meta = {
...(route.meta || {})
}
meta.requireOps = normalizeRequireOps(meta.requireOps)
return route
}
permissionRoutes = routeMap(permissionRoutes, normalizeRouteMeta)
const permissionRoutesCopy = JSON.parse(JSON.stringify(permissionRoutes))
После получения списка операций вам нужно только пройти маршрут разрешений, а затем запроситьrequireOpsПредставленное действие отсутствует в списке действий. Здесь нужно разобратьсяrequireOpsЕсли он не установлен, если все подмаршруты являются маршрутами разрешений, его необходимо автоматически добавить к родительскому маршруту.requireOpsВ противном случае, когда все дочерние маршруты не имеют полномочий, родительский маршрут считается неавторизованным и доступным; и если только один из дочерних маршрутов не имеет полномочий, родительский маршрут не нужно обрабатывать. Итак, здесь вы можете использовать рекурсию для решения, сначала обработать дочерний маршрут, а затем обработать родительский маршрут.
const filterPermissionRoutes = (routes, cb) => {
// 可能父路由没有设置requireOps 需要根据子路由确定父路由的requireOps
routes.forEach(route => {
if (route.children) {
route.children = filterPermissionRoutes(route.children, cb)
if (!route.meta.requireOps) {
const hasNoPermission = route.children.some(child => child.meta.requireOps === null)
// 如果子路由中存在不需要权限控制的路由,则跳过
if (!hasNoPermission) {
route.meta.requireOps = [].concat(...route.children.map(child => child.meta.requireOps))
}
}
}
})
return cb(routes)
}
Затем отфильтруйте маршрутизацию разрешений на основе списка действий.
let operations = null // 从后端获取后更新它
const hasOp = opcode => operations
? operations.some(op => op.opcode === opcode)
: false
const proutes = filterPermissionRoutes(permissionRoutes, routes => routes.filter(route => {
const requireOps = route.meta.requireOps
if (requireOps) {
return requireOps.some(hasOp)
}
return true
}))
// 动态添加路由
router.addRoutes(proutes)
Функциональные компоненты управляют локальными разрешениями
Реализация этого компонента очень проста, разрешение оценивается по пришедшему опкоду, если он проходит, он возвращает содержимое слота, иначе возвращает ноль. Кроме того, в целях унификации стиля, поддержкаrootсвойство, представляющее корневой узел компонента
const AccessControl = {
functional: true,
render (h, { data, children }) {
const attrs = data.attrs || {}
// 如果是root,直接透传
if (attrs.root !== undefined) {
return h(attrs.root || 'div', data, children)
}
if (!attrs.opcode) {
return h('span', {
style: {
color: 'red',
fontSize: '30px'
}
}, '请配置操作码')
}
const opcodes = attrs.opcode.split(',')
if (opcodes.some(hasOp)) {
return children
}
return null
}
}
Динамически генерировать меню разрешений
Возьмем в качестве примера ElementUI, поскольку для динамического рендеринга требуется рекурсия, если он в виде файлового компонента, будет дополнительный слой корневых компонентов, поэтому вот простой пример с функцией рендеринга, которую можно модифицировать в соответствии с ваши собственные потребности.
// 权限菜单组件
export const PermissionMenuTree = {
name: 'MenuTree',
props: {
routes: {
type: Array,
required: true
},
collapse: Boolean
},
render (h) {
const createMenuTree = (routes, parentPath = '') => routes.map(route => {
// hidden: 为true时当前菜单和子菜单都不显示
if (route.hidden === true) {
return null
}
// 子路径处理
const fullPath = route.path.charAt(0) === '/' ? route.path : `${parentPath}/${route.path}`
// visible: 为false时不显示当前菜单,但显示子菜单
if (route.visible === false) {
return createMenuTree(route.children, fullPath)
}
const title = route.meta.title
const props = {
index: fullPath,
key: route.path
}
if (!route.children || route.children.length === 0) {
return h(
'el-menu-item',
{ props },
[h('span', title)]
)
}
return h(
'el-submenu',
{ props },
[
h('span', { slot: 'title' }, title),
...createMenuTree(route.children, fullPath)
]
)
})
return h(
'el-menu',
{
props: {
collapse: this.collapse,
router: true,
defaultActive: this.$route.path
}
},
createMenuTree(this.routes)
)
}
}
Контроль доступа к интерфейсу
Обычно мы используем axios. Здесь нам нужно всего лишь добавить несколько строк кода в пакет axios. Существует много способов упаковать axios. Вот простой пример.
const ajax = axios.create(/* config */)
export default {
post (url, data, opcode, config = {}) {
if (opcode && !hasOp(opcode)) {
return Promise.reject(new Error('没有操作权限'))
}
return ajax.post(url, data, { /* config */ ...config }).then(({ data }) => data)
},
// ...
}
На данный момент решение почти завершено, и можно выполнить визуализацию конфигурации разрешений в соответствии со списком операций.routeNameДля этого сопоставьте операцию с маршрутом разрешений один к одному, вdemoЕсть простая реализация
Ссылаться на
Рука об руку, вы попадете на задний план с vue series 2 (разрешение на вход)