Зберігання елементів меню з дозволом користувача


11

Я створюю систему меню в PHP та MySQL. У мене буде кілька різних меню, і кожне меню матиме набір елементів, підключених до нього.

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

Я маю поки що щось подібне:

-------------------
|Menus
-------------------
|id| |display name|
-------------------

----------------------------------------------------------
|Menu items
----------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| |permission|
----------------------------------------------------------

Я думаю, що permissionстовпець може бути або рядком, розділеним комою, і я можу відповідати поточному ідентифікатору дозволу користувача. Це також може бути посиланням на якусь іншу таблицю, яка визначає всі можливі комбінації існуючих на даний момент дозволів.

Одним із рішень може бути також просто зберігати кілька пунктів меню, де єдиною різницею є дозвіл, хоча це може призвести до повторного зберігання даних і, можливо, до адміністрування.

Мені б хотілося почути деякі думки про те, як це структурувати і що можна вважати чистим, динамічним і хитрим.

Дякую.


Чи мають у користувачів щось спільне? Зазвичай ви об'єднуєте елементи меню у функціональні групи та призначаєте користувачів цим групам (наприклад, користувачам адміністратора, користувачам БД, торговцям тощо). Потім ви просто керуєте угрупованнями, залежно від вибору технології - цим можна керувати, використовуючи щось на зразок Active Directory.
Майкл

1
Ви отримуєте тут багато хороших відповідей, але те, що ви можете прочитати, - це ACL. en.wikipedia.org/wiki/Access_control_list
Reactgular

Відповіді:


18

Я буду моделювати його за допомогою діаграми ER.

  • А PERMISSION- це доступ, наданий ROLEна дану особу MENU_ITEM.
  • ROLE - це набір заздалегідь визначених дозволів, яким надається ім'я
  • А USERможе мати безліч ROLE, наданих йому.
  • Маючи дозволи, призначені для ролей замість користувачів, значно полегшує адміністрування дозволів.

введіть тут опис зображення

Тоді ви можете створити представлення, щоб не потрібно писати приєднання щоразу:

create or replace view v_user_permissions
select
    distinct mi.id, mi.menu_id. mi.label. mi.link, mi.parent, mi.sort, u.user_id
from
    user u
    join user_role ur on (u.user_id = ur.user_id)
    join role ro on (ur.role_id = role.role_id)
    join permission per on (ro.role_id = per.role_id)
    join menu_item mi on (per.metu_item_id = mi.metu_item_id)

Потім кожного разу, коли ви хочете дізнатися, до яких пунктів меню користувач має доступ, ви можете запитувати його:

select * from v_user_permissions up where up.user_id = 12736 order by up.sort

Редагувати:

Оскільки користувачеві може бути надано кілька ролей, дозволи дозволу ролей можуть перетинатися, тобто дві окремі ролі можуть мати доступ до одного і того ж пункту меню. Коли ви визначаєте роль, ви не знаєте заздалегідь, чи буде вона мати деякі спільні з іншими ролями дозволи. Але оскільки мова йде про об'єднання множин, має значення лише те, чи наданий дозвіл є частиною набору, а не скільки разів він з’являється, отже, і distinctпункт у перегляді.


Дивовижне, спасибі Не можу зрозуміти, чому я просто не міг сам це намалювати. Здогадуюсь, мене заблокували та не досвідчені :)
Протяг

Ек, я думав, що зрозумів, але очевидно, що ні. Як я можу мати кілька дозволів для одного пункту меню?
проміжок

1
@span Оскільки користувачеві можуть бути надані кілька ролей, а дозволи на ролі можуть перетинатися, тобто дві різні ролі можуть мати доступ до одного пункту меню. Коли ви визначаєте роль, заздалегідь не знаєте, чи буде вона надана разом з іншими, які мають для неї певні дозволи. Але оскільки ця проблема стосується об'єднання множин, має значення лише те, чи є даний дозвіл частиною набору чи ні, а не скільки разів він з'являється.
Тулен Кордова

Дякую, я продовжую читати вашу відповідь, поки не отримаю її;). Я думаю, що моя помилка полягала в думці, що єдиний дозвіл можна використовувати разом з роллю для розділення пунктів меню. Здається, мені потрібен дозвіл на кожен "тип" пункту меню. Ще раз дякую за вашу допомогу! Я намалюю кілька діаграм Венна і побачу, чи зможу я правильно обернути голову \ o /
проліт

5

Список списків, розділених комами, означає порівняння підрядків кожного разу, коли ви робите запит до меню. Це менше, ніж ідеально.

Потрібно нормалізувати таблицю:

---------------------------------------------
|Menu items
---------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort|
---------------------------------------------

+---------------------------+
| menu_permission           |
|---------------------------|
| |menu_permission_id| (pk) |
| |menu_id| (fk)            |
| |permission|              |
+---------------------------+

Якщо ви все ще хочете, щоб список відокремлений комами (чомусь), ви можете витягнути його з такими речами, як group_concatу mysql wm_concat в oracle або подібними функціями інших мов.

Перевага для цього багаторазова.

По-перше, є практичність розмови. Якщо зробити підрядку проти довільно великої рядка (якщо ви виправите її розмір, пізніше у вас можуть виникнути проблеми із заповненням рядка, щоб ви почали отримувати дозволи, як aзамість another_permission), означає сканувати рядок у кожному рядку. Це не те, для чого бази даних оптимізовані.

По-друге, запит, який ви пишете, стає набагато простішим. Щоб визначити, чи існує дозвіл 'foo' у списку, розділеному комами, потрібно перевірити "foo".

... permission like "%foo%" ...

Однак це дасть помилковий позитив, якщо ви також маєте дозвіл "foobar". Тому зараз вам потрібно мати тест

... permission like "%,foo,%" ...

але це дасть помилковий мінус, якщо 'foo' знаходиться на початку або в кінці рядка. Це призводить до чогось подібного

... (permission like "foo,%" 
  or permission like "%,foo,%" 
  or permission like "%,foo" ) ...

Ви зауважите, що цілком ймовірно, що вам потрібно буде зробити кілька сканувань рядка. Цей шлях призводить до божевілля.

Зауважте, що з урахуванням всього цього вам не вистачає практичної здатності робити прив'язку параметрів (все-таки можливо, просто стає ще більш потворно).

Нормалізація поля дає значно більшу гнучкість та розбірливість у вашій базі даних. Ви не пошкодуєте про це.


Дякую за вашу чудову відповідь, це дало мені більше знань, і я дуже радий за це, хоча, думаю, рішення user61852 зараз найкраще підійде.
проміжок

3

Цей класичний підхід до цього є, User -> UserGroupа потім пов'язаний з Menu -> MenuItem -> UserGroup. Використання цілого значення для зважування рівня дозволу.

-------------------
|Menu
-------------------
|id| |display name|
-------------------

-------------------------------------------------------------
|Menu Item
-------------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| | min_option1
-------------------------------------------------------------

-------------------------------------------
| User
-------------------------------------------
|id| | username | password | user_group_id
-------------------------------------------

-------------------------------------------
| User Group
-------------------------------------------
|id| option1 | option2 | option2
-------------------------------------------

Коли потрібно відобразити меню для поточного користувача. Ви можете запитувати подібну базу даних.

SELECT * FROM `MenuItem`
    LEFT JOIN `UserGroup` ON (`MenuItem`.`user_group_id` = `UserGroup`.`id`)
    LEFT JOIN `User` ON (`UserGroup`.`id` = `User`.`user_group_id` AND `User`.`id` = $current_user_id)
        WHERE `MenuItem`.`menu_id` = $menu_id AND `UserGroup`.`option1` >= `MenuItem`.`min_option1`;

Це вибере лише меню, видимі поточному користувачеві на основі умови для option1.

Крім того, якщо ви зберігаєте дані поточної групи користувачів у поточному сеансі, то ніяке приєднання не потрібно.

SELECT * FROM `MenuItem` WHERE `MenuItem`.`menu_id` = $menu_id AND `MenuItem`.`min_option1` >= $user_group_option1;

Коли ви говорите про бажання зберігати кілька дозволів на пункт меню. Буду обережним, щоб не плутати ролі користувачів та логіку бізнесу.


2
Цей дизайн дозволив би прив’язати кожен MenuItem до однієї групи користувачів, тобто обмежене меню або дублювання даних. Насправді ви хочете таблицю посилань, в ідеалі. Також ваш вибір іменування таблиці БД як множини змушує мене сумувати;)
Ед Джеймс

@EdWoodcock о, дуже хороший момент. Я повинен був пройти рівень дозволу (int), а потім порівняти його з рівнем групи користувачів. Я це зміню. Зауважте, звичка множини назв, викликана моїм використанням CakePHP. Що дивно, оскільки фреймворк використовує сингулярні псевдоніми для таблиць у запитах.
Реакційний

@MatthewFoscarini Не хвилюйся, я насправді не турбую, доки послідовність кодової бази;)
Ед Джеймс

1
Дивовижна відповідь. Я буду мати це на увазі наступного разу, коли я щось зроблю з цим. На даний момент я думаю, що рішення user61852 найкраще підійде, оскільки не потребує стільки змін до існуючого коду. Дякую!
проміжок
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.