В процессе рефакторинга всего фронтенд-кода последнего проекта также была изменена структура управления правами доступа. Управление правами является перекрестным аспектом, используемым на всем сервисе, включая настройки маршрутизатора, логику рендеринга меню и отдельные компоненты экранов. Благодаря рефакторингу эта логика была развита в более декларативную и согласованную структуру управления. Улучшенные части можно разделить на три крупных направления.
-
Интеграция логики доступа к страницам:Я изменил код для управления правами в роутере и меню, чтобы управлять ими из одного файла.
-
Унификация внутреннего кода экрана:Способ проверки прав различен на каждом экране, поэтому мы объединили его в один кастомный хук для управления.
-
Оптимизация производительности: Мы улучшили часть, где расчет прав выполнялся повторно при каждом рендеринге, используя useMemo, чтобы оптимизировать его так, чтобы он пересчитывался только при изменении информации о правах.
Интеграция стандартов прав для маршрутизатора и меню: PAGE_PERMISSIONS
В существующей структуре определение прав доступа к определенной странице было фрагментировано между логикой охраны маршрутизатора и рендерингом бокового меню. Из-за этого каждый раз, когда происходили изменения прав в проекте, приходилось искать и исправлять все связанные файлы, и если хотя бы одно из них было пропущено, возникали проблемы с последовательностью: меню отображалось, но доступ был заблокирован, или, наоборот, доступ был открыт, но меню не отображалось.
Чтобы решить эту проблему, мы разработали единый документ прав PAGE_PERMISSIONS, использующий константу PATHS, в которой определены все маршруты проекта, в качестве ключей, и хранящую разрешенные роли для каждого маршрута в виде массива.
|
экспорт const PAGE_PERMISSIONS: Record = { [PATHS.NOTICE_LIST]: ['ПОЛЬЗОВАТЕЛЬ', 'МЕНЕДЖЕР', 'АДМИН'], [PATHS.NOTICE_DETAIL]: ['УПРАВЛЯЮЩИЙ', 'АДМИН'], [PATHS.ORG_MANAGEMENT]: ['АДМИН'], ; |
Таким образом, если создать карту прав, то в роутер-гварде или компоненте меню больше не нужно будет писать отдельную логику, достаточно передать текущий путь (Path) и просто проверить разрешенные роли в этом объекте.
|
<RoleCheckLayout allowedRoles={PAGE_PERMISSIONS[PATHS.NOTICE_LIST]}> <NoticeListPage /> </RoleCheckLayout> |
Этот метод настраивает права доступа Полностью отделяет от бизнес-логикиЭто позволяет управлять декларативно. Даже если в будущем будут добавлены новые роли или усилены политики безопасности для отдельных страниц, достаточно изменить только один объект PAGE_PERMISSIONS, чтобы мгновенно применить изменения ко всему сервису.
Собрать разбросанную внутреннюю логику в одно: useAuthCheck
Кроме контроля доступа на уровне страниц, важной задачей также была детальная логика управления пользовательским интерфейсом, такая как отображение определённых кнопок или деактивация форм внутри экрана. Ранее у нас была практика получения состояния пользователя прямо в каждом компоненте и выполнения операции includes или многократного написания сложных условий.
Эти фрагментированные логики следует объединить в один кастомный хук под названием useAuthCheck хукиМы абстрагировались. Особенно этот хук включает в себя логику, которая объединяет roleNames, управляемые глобальным состоянием, и tenantRoleNames, связанные с определёнными арендаторами или организациями, что позволяет разработчикам интуитивно использовать его даже в проектах со сложной структурой прав.
|
export const useAuthCheck = () => { const { roleNames = [], tenantRoleNames = [] } = useAuth(); const hasRole = useCallback( (роль: RoleName) => [...roleNames, ...tenantRoleNames].включает(роль), [roleNames, tenantRoleNames] ); const { isUser, isManager, isAdmin } = useMemo(() => ({ isUser: имеетРоль(RoleName.USER), isManager: имеетРоль(RoleName.MANAGER) || имеетРоль(RoleName.ADMIN), isAdmin: hasRole(RoleName.ADMIN), }), [hasRole]); возврат { hasRole, isUser, isManager, isAdmin }; }; |
На этапе реализации пользовательского интерфейса можно использовать очень лаконичный код, как показано ниже.
|
const { isAdmin, isManager } = useAuthCheck(); вернуть ( <> {isManager && исправить} {isAdmin && удалить} ; |
Наибольшим преимуществом, полученным благодаря внедрению этого хука, является Гибкая структура для измененийЕсли определение 'менеджера' изменится, чтобы включать 'старших пользователей', то вместо изменения кода на множестве экранов достаточно будет изменить только логику вычисления isManager внутри useAuthCheck. Это значительно снизило количество человеческих ошибок и улучшило читаемость кода.
Оптимизация производительности и стабильность: использование useMemo и useCallback
Логика проверки прав доступа имеет свойство повторно выполняться каждый раз при повторной отрисовке компонента. Особенно, если имеется множество ролей пользователей или если операции объединения и итерации массивов, такие как [...roleNames, ...tenantRoleNames], включены, это может привести к ненужным затратам на вычисления. Кроме того, если функция создается каждый раз заново, это может вызвать ненужную повторную отрисовку дочерних компонентов, которые принимают ее в качестве свойств.
В процессе рефакторинга мы активно использовали оптимизационные API React, чтобы минимизировать эти ненужные накладные расходы на операции.
-
Предотвращение повторного создания функцийОбертка функции hasRole в useCallback сохранила одинаковую ссылку, если массив зависимостей, такой как roleNames, не меняется. Это позволяет оптимизировать дочерние компоненты, использующие эту функцию (например, React.memo).
-
Кэширование производного состоянияРезультаты определения ролей, такие как isAdmin и isManager, имеют чисто функциональный характер, поэтому они были кэшированы с помощью useMemo. Это предотвращает повторные вычисления сложных комбинаций прав при каждом рендеринге и позволяет повторно использовать предыдущее значение, если результаты вычислений одинаковы.
Такая оптимизация не только ускоряет выполнение, но также эффективна с точки зрения обеспечения согласованности данных. Даже если информация о правах временно становится нестабильной во время рендеринга, это позволяет поддерживать стабильный интерфейс пользователя благодаря мемоизированным значениям.
Основная цель этой реорганизации — унификация управления правами доступа. Объединив фрагментированную логику в четкий единый интерфейс PAGE_PERMISSIONS и useAuthCheck, мы получили следующие практические выгоды.
-
Максимальная эффективность обслуживания: При изменении политики прав доступ к редактированию ограничен одним файлом, что ускорило скорость реагирования.
-
Повышение декларативности кодаТеперь код может сосредоточиться не на "как проверить права", а на "кто может получить доступ к этой функции".
-
Обеспечение стабильности системы: Я предотвратил уязвимости безопасности, связанные с отсутствием или несоответствием прав, с помощью централизованного управления.
Хотя это казалось простой задачей — объединить фрагментированную логику в одно целое, мы смогли поймать двух зайцев: улучшить читаемость и поддерживаемость, а также заложить прочную основу для надежного масштабирования, даже когда услуги станут более сложными в будущем.
Линн