Решение Vue для управления разрешениями RBAC на основе d2-admin

Vue.js

Первые две статьи о маршрутизации разрешений vue заполнили яму и сказали много теорий, пора управлять волной.

Резюме реализации маршрутизации разрешений vue

Сводка реализации маршрутизации разрешений Vue 2

выберитеd2-adminЭто потому, что в связанных проектах с открытым исходным кодом element-ui структура и код d2-admin позволяют мне чувствовать себя наиболее комфортно, а также очень удобно реализовать управление полномочиями RBAC на основе d2-admin, и нет серьезные навязчивые изменения в d2-admin. .

адрес предварительного просмотра

Github

Связанные концепции

Если вы не знаете RBAC, вы можете посмотреть здесьАрхитектура разделения интерфейсов и серверных частей системы управления предприятием Серия 1 Модель разрешений

权限模型

  • Реализован контроль разрешений модели RBAC.
  • Меню и маршрутизация управляются независимо и полностью возвращаются серверной частью.
  • userхранить пользователя
  • adminОпределяет, является ли пользователь системным администратором
  • roleХранить информацию о роли
  • roleUserХраните ассоциацию между пользователями и ролями
  • menuИнформация о меню магазина, тип菜单а также功能, в одном меню может быть несколько функций,菜单ТипpermissionПоле определяет функциональное разрешение, необходимое для доступа к этому меню,功能ТипpermissionПоле эквивалентно другому имени этой функции, поэтому菜单Типpermissionполе для одного из功能тип дочернего узлаpermissionстоимость
  • permissionХраните взаимосвязь между ролями и функциями
  • interfaceСохранить информацию об интерфейсе
  • functionInterfaceХраните отношения между функциями и интерфейсами.Просматривая роль пользователя, а затем просматривая функциональные разрешения соответствующей роли, а затем через соответствующую функцию, вы можете узнать интерфейсы, к которым пользователь может получить доступ
  • routeХранить информацию о маршрутизации переднего плана черезpermissionПоле отфильтровывает маршруты, к которым может получить доступ пользователь.

Запуск процесса и связанный с ним API

использоватьd2adminВ соответствии с исходной логикой входа глобальная защита маршрутизации оценивает, была ли получена информация о разрешении, и после ее получения помечает ее как полученную.

const token = util.cookies.get('token')
    if (token && token !== 'undefined') {
      //拉取权限信息
      if (!isFetchPermissionInfo) {
        await fetchPermissionInfo();
        isFetchPermissionInfo = true;
        next(to.path, true)
      } else {
        next()
      }
    } else {
      // 将当前预计打开的页面完整地址临时存储 登录后继续跳转
      // 这个 cookie(redirect) 会在登录后自动删除
      util.cookies.set('redirect', to.fullPath)
      // 没有登录的时候跳转到登录界面
      next({
        name: 'login'
      })
    }
//标记是否已经拉取权限信息
let isFetchPermissionInfo = false

let fetchPermissionInfo = async () => {
  //处理动态添加的路由
  const formatRoutes = function (routes) {
    routes.forEach(route => {
      route.component = routerMapComponents[route.component]
      if (route.children) {
        formatRoutes(route.children)
      }
    })
  }
  try {
    let userPermissionInfo = await userService.getUserPermissionInfo()
    permissionMenu = userPermissionInfo.accessMenus
    permissionRouter = userPermissionInfo.accessRoutes
    permission.functions = userPermissionInfo.userPermissions
    permission.roles = userPermissionInfo.userRoles
    permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)
    permission.isAdmin = userPermissionInfo.isAdmin == 1
  } catch (ex) {
    console.log(ex)
  }
  formatRoutes(permissionRouter)
  let allMenuAside = [...menuAside, ...permissionMenu]
  let allMenuHeader = [...menuHeader, ...permissionMenu]
  //动态添加路由
  router.addRoutes(permissionRouter);
  // 处理路由 得到每一级的路由设置
  store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
  // 设置顶栏菜单
  store.commit('d2admin/menu/headerSet', allMenuHeader)
  // 设置侧边栏菜单
  store.commit('d2admin/menu/fullAsideSet', allMenuAside)
  // 初始化菜单搜索功能
  store.commit('d2admin/search/init', allMenuHeader)
  // 设置权限信息
  store.commit('d2admin/permission/set', permission)
  // 加载上次退出时的多页列表
  store.dispatch('d2admin/page/openedLoad')
  await Promise.resolve()
}

Информация о разрешениях, которую серверная часть должна вернуть, включает в себя набор кодов ролей, набор кодов функций, набор информации об интерфейсе, список меню, список маршрутов, а также информацию о том, идентифицирован ли системный администратор после фильтрации разрешений. Формат следующий

{
  "statusCode": 200,
  "msg": "",
  "data": {
    "userName": "MenuManager",
    "userRoles": [
      "R_MENUADMIN"
    ],
    "userPermissions": [
      "p_menu_view",
      "p_menu_edit",
      "p_menu_menu"
    ],
    "accessMenus": [
      {
        "title": "系统",
        "path": "/system",
        "icon": "cogs",
        "children": [
          {
            "title": "系统设置",
            "icon": "cogs",
            "children": [
              {
                "title": "菜单管理",
                "path": "/system/menu",
                "icon": "th-list"
              }
            ]
          },
          {
            "title": "组织架构",
            "icon": "pie-chart",
            "children": [
              {
                "title": "部门管理",
                "icon": "html5"
              },
              {
                "title": "职位管理",
                "icon": "opencart"
              }
            ]
          }
        ]
      }
    ],
    "accessRoutes": [
      {
        "name": "System",
        "path": "/system",
        "component": "layoutHeaderAside",
        "componentPath": "layout/header-aside/layout",
        "meta": {
          "title": "系统设置",
          "cache": true
        },
        "children": [
          {
            "name": "MenuPage",
            "path": "/system/menu",
            "component": "menu",
            "componentPath": "pages/sys/menu/index",
            "meta": {
              "title": "菜单管理",
              "cache": true
            }
          },
          {
            "name": "RoutePage",
            "path": "/system/route",
            "component": "route",
            "componentPath": "pages/sys/route/index",
            "meta": {
              "title": "路由管理",
              "cache": true
            }
          },
          {
            "name": "RolePage",
            "path": "/system/role",
            "component": "role",
            "componentPath": "pages/sys/role/index",
            "meta": {
              "title": "角色管理",
              "cache": true
            }
          },
          {
            "name": "UserPage",
            "path": "/system/user",
            "component": "user",
            "componentPath": "pages/sys/user/index",
            "meta": {
              "title": "用户管理",
              "cache": true
            }
          },
          {
            "name": "InterfacePage",
            "path": "/system/interface",
            "component": "interface",
            "meta": {
              "title": "接口管理"
            }
          }
        ]
      }
    ],
    "accessInterfaces": [
      {
        "path": "/menu/:id",
        "method": "get"
      },
      {
        "path": "/menu",
        "method": "get"
      },
      {
        "path": "/menu/save",
        "method": "post"
      },
      {
        "path": "/interface/paged",
        "method": "get"
      }
    ],
    "isAdmin": 0,
    "avatarUrl": "https://api.adorable.io/avatars/85/abott@adorable.png"
  }
}

меню настроек

закрепит меню (/menu/header,/menu/aside) и меню разрешений, возвращаемое серверной частью (accessMenus) объединяется и сохраняется в соответствующем модуле хранилища vuex.

...
let allMenuAside = [...menuAside, ...permissionMenu]
let allMenuHeader = [...menuHeader, ...permissionMenu]
...
// 设置顶栏菜单
store.commit('d2admin/menu/headerSet', allMenuHeader)
// 设置侧边栏菜单
store.commit('d2admin/menu/fullAsideSet', allMenuAside)
// 初始化菜单搜索功能
store.commit('d2admin/search/init', allMenuHeader)

обрабатывать маршрутизацию

Использовать по умолчаниюrouterMapComponentsспособ обработки маршрута разрешений, возвращаемого серверной частью

//处理动态添加的路由
const formatRoutes = function (routes) {
    routes.forEach(route => {
        route.component = routerMapComponents[route.component]
        if (route.children) {
        formatRoutes(route.children)
        }
    })
}
...
formatRoutes(permissionRouter)
//动态添加路由
router.addRoutes(permissionRouter);
// 处理路由 得到每一级的路由设置
store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])

Методы обработки маршрутизации и различия можно увидетьСводка реализации маршрутизации разрешений Vue 2

Установить информацию о разрешении

Сохраните набор кодов ролей, набор кодов функций, набор информации об интерфейсе и сохраните ли идентификатор системного администратора в соответствующем модуле хранилища vuex.

...
permission.functions = userPermissionInfo.userPermissions
permission.roles = userPermissionInfo.userRoles
permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces)
permission.isAdmin = userPermissionInfo.isAdmin == 1
...
// 设置权限信息
store.commit('d2admin/permission/set', permission)

Контроль разрешений интерфейса и загрузка конфигурации

Поддержка использования ролевого кодирования, функционального кодирования и разрешений интерфейса для управления следующим образом.

export function getMenuList() {
    return request({
        url: '/menu',
        method: 'get',
        interfaceCheck: true,
        permission:["p_menu_view"],
        loading: {
            type: 'loading',
            options: {
                fullscreen: true,
                lock: true,
                text: '加载中...',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.8)'
            }
        },
        success: {
            type: 'message',
            options: {
                message: '加载菜单成功',
                type: 'success'
            }
        }
    })
}

interfaceCheck: trueУказывает, что разрешения интерфейса используются для управления.Если информация об интерфейсе, хранящаяся в хранилище vuex, соответствует запрашиваемому интерфейсу, запрос может быть инициирован, в противном случае запрос будет перехвачен.

permission:["p_menu_view"]Указывает, что код роли и код функции используются для проверки разрешения.Если код роли или код функции, хранящийся в хранилище vuex, совпадает с кодом, представленным в настоящее время, запрос может быть инициирован, в противном случае запрос будет перехвачен.

Исходный код находится по адресуlibs/permission.js, который может быть изменен в соответствии с вашими потребностями

loadingИсходный код, связанный с конфигурацией, находится вlibs/loading.js, настройте его в соответствии с вашими потребностями,successТо же самое, исходный код находится вlibs/loading.js. Таким образом, вы можете самостоятельно настроить другие функции, например, отказ от запроса.

Управление правами доступа к элементам страницы

инструкции по использованиюv-permission:

 <el-button
    v-permission:function.all="['p_menu_edit']"
    type="primary"
    icon="el-icon-edit"
    size="mini"
    @click="batchEdit"
    >批量编辑</el-button>

Параметры могут бытьfunction,role, указывающий, что код функции или код роли используется для проверки, а если он пуст, то оба используются для проверки.

модификаторall, указывая, что все кодировки в значении инструкции должны совпадать.

Исходный код находится по адресуplugin/permission/index.jsи измените его в соответствии с вашими фактическими потребностями.

использоватьv-if+ глобальный метод:

<el-button
    v-if="canAdd"
    type="primary"
    icon="el-icon-circle-plus-outline"
    size="mini"
    @click="add"
    >添加</el-button>
data() {
    return {
      canAdd: this.hasPermissions(["p_menu_edit"])
    };
  },

По умолчанию для проверки используются как код роли, так и код функции, и достаточно одного совпадения.

Аналогичный метод такжеhasFunctions,hasRoles.

Исходный код находится по адресуplugin/permission/index.js, и измените его в соответствии с вашими реальными потребностями.

Не используйтеv-if="hasPermissions(['p_menu_edit'])"Таким образом, метод будет выполняться несколько раз.

Вы также можете напрямую прочитать информацию о разрешениях из хранилища vuex в компоненте для проверки.

консультации по развитию

  • Компоненты уровня страницы размещаются вpages/каталог и вrouterMapCompnonents/index.jsЭкспортируется в виде ключ-значение

  • Фиксированные меню, не требующие контроля разрешений, помещаются вmenu/aside.jsа такжеmenu/header.jsсередина

  • Маршруты, не требующие контроля разрешений, помещаются вrouter/routes.js frameInВнутри

  • Меню и маршруты, требующие контроля разрешений, добавляются через функцию управления интерфейсом, чтобы гарантировать, что менюpathс маршрутизациейpathСоответственно, маршрутизируетсяnameс компонентами страницыnameбыть последовательнымkeep-aliveэффективный, направленныйcomponentсуществуетrouterMapCompnonents/index.jsможно подобрать по ключу.

  • Меню и маршрутизация этапов разработки разработчиками для добавления собственного обслуживания и поддержания списка инвентаризации после линейки соответствующим людям для поддержания может.

Если вы чувствуете себя хлопотно и не хотите, чтобы меню и маршрут возвращались серверной частью, вы можете сохранить меню и маршрут во внешнем интерфейсе (тот, что в маршруте).componentИли используйте строки, см.mock/permissionMenuAndRouter.js) и поддерживать соответствующие коды разрешений в меню и маршрутах, обычно используя функциональные коды. Серверной части не нужно возвращать информацию о меню и маршрутизации, но по-прежнему требуется другая информация о разрешениях, такая как коды ролей, коды функций и т. д. Через список кодов функций, возвращаемый серверной частью, клиентская часть отфильтровывает меню и маршруты, на которые у пользователя есть разрешения, а формат отфильтрованных меню и маршрутов совпадает с форматом, возвращаемым серверной частью. раньше, а затем обработанные меню и маршруты рассматриваются как бэкэнд, который может обрабатываться так же, как и возвращаемый терминалом.

Имитация данных и генерация кода

Имитация использования данныхlazy-mockмодифицированныйd2-admin-server, данные действительно поступают из серверной части. По сравнению с другими инструментами он поддерживает сохранение данных. Для хранения используются файлы JSON, и нет необходимости устанавливать базу данных. Простая конфигурация может автоматически генерировать интерфейс добавления, удаления, изменения и проверки.

Серверная часть использует промежуточное программное обеспечение для управления правами доступа, например:

 .get('/menu', PermissionCheck(), controllers.menu.getMenuList)

PermissionCheckПо умолчанию интерфейс используется для проверки, чтобы убедиться, что API, к которым пользователь может получить доступ, соответствуют текущему API, а код функции и код роли поддерживаются для проверки.PermissionCheck(["p_menu_edit"],["r_menu_admin"],true), Первый параметр — это код функции, второй — код роли, а третий — использовать ли интерфейс для проверки.

Для более подробного использования см.ленивая макетная документация

Генерация внешнего кода все еще находится в разработке...