sql >> Databasteknik >  >> RDS >> Database

Hantera roller och statusar i ett system

Det finns många sätt att lösa ett problem, och det är fallet med att administrera roller och användarstatus i mjukvarusystem. I den här artikeln hittar du en enkel utveckling av den idén samt några användbara tips och kodexempel.

Grundidé

I de flesta system finns det vanligtvis ett behov av att ha roller och användarstatus .

Roller är relaterade till rättigheter som användare har när de använder ett system efter att ha lyckats logga in. Exempel på roller är "callcentermedarbetare", "callcenterchef", "backofficemedarbetare", "backofficechef" eller "chef". Generellt betyder det att en användare kommer att ha tillgång till viss funktionalitet om han eller hon har rätt roll. Det är klokt att anta att en användare kan ha flera roller samtidigt.

Statuser är mycket strängare och de avgör om användaren har rättigheter att logga in i systemet eller inte. En användare kan bara ha en status vid en tid. Exempel på status kan vara:"arbetar", "på semester", "sjukskriven", "kontrakt avslutat".

När vi ändrar en användares status kan vi fortfarande behålla alla roller relaterade till den användaren oförändrade. Det är mycket användbart eftersom vi oftast bara vill ändra användarens status. Om en användare som arbetar som callcenteranställd åker på semester kan vi helt enkelt ändra hans status till "på semester" och återställa den till statusen "arbetar" när han kommer tillbaka.

Genom att testa roller och statuser under inloggningen kan vi bestämma vad som ska hända. Till exempel kanske vi vill förbjuda inloggning även om användarnamn och lösenord är korrekta. Vi skulle kunna göra det om den aktuella användarstatusen inte innebär att han arbetar eller om användaren inte har någon roll i systemet.

I alla modeller nedan, tabellerna status och role är desamma.

Tabell status har fälten id och status_name och attributet is_active . Om attributet is_active är satt till "True", det betyder att användaren som har den statusen för närvarande arbetar. Till exempel skulle statusen "fungerar" ha attributet is_active med värdet True, medan andra ("på semester", "sjukskriven", "kontrakt avslutat") skulle ha värdet False.

Rolltabellen har bara två fält:id och role_name .

user_account tabellen är densamma som user_account tabell som presenteras i den här artikeln. Endast i den första modellen görs user_account Tabellen innehåller två extra attribut (role_id och status_id ).

Några modeller kommer att presenteras. Alla fungerar och kan användas men har sina fördelar och nackdelar.

Enkel modell

Den första idén kan vara att vi helt enkelt lägger till främmande nyckelrelationer till user_account tabell, med hänvisning till tabeller status och role . Båda role_id och status_id är obligatoriska.




Det här är ganska enkelt att designa och även att hantera data med frågor men har några nackdelar:

  1. Vi behåller ingen historik (eller framtida) data.

    När vi ändrar status eller roll uppdaterar vi helt enkelt status_id och role_id i user_account tabell. Det kommer att fungera bra för nu, så när vi gör en förändring kommer det att återspeglas i systemet. Detta är ok om vi inte behöver veta hur status och roller har förändrats historiskt. Det finns också ett problem i att vi inte kan lägga till framtid roll eller status utan att lägga till extra tabeller till denna modell. En situation där vi förmodligen skulle vilja ha det alternativet är när vi vet att någon kommer att vara på semester från och med nästa måndag. Ett annat exempel är när vi har en ny medarbetare; kanske vill vi gå in i hans status och roll nu och att det ska bli giltigt någon gång i framtiden.

    Det finns också en komplikation om vi har schemalagda händelser som använder roller och statuser. Händelser som förbereder data för nästa arbetsdag körs vanligtvis medan de flesta användare inte använder systemet (t.ex. nattetid). Så om någon inte kommer att arbeta i morgon måste vi vänta till slutet av den aktuella dagen och sedan ändra hans roller och status efter behov. Till exempel, om vi har anställda som för närvarande arbetar och har rollen "anställd på callcenter", kommer de att få en lista över kunder som de måste ringa. Om någon av misstag hade den statusen och rollen kommer han också att få sina kunder och vi måste lägga tid på att rätta till det.

  2. Användare kan bara ha en roll åt gången.

    Generellt bör användare kunna ha mer än en roll i systemet. Kanske finns det inget behov av något sådant när du designar databasen. Tänk på att förändringar i arbetsflödet/processen kan inträffa. Till exempel kan klienten någon gång bestämma sig för att slå samman två roller till en. En möjlig lösning är att skapa en ny roll och tilldela alla funktioner från de tidigare rollerna till den. Den andra lösningen (om användare kan ha mer än en roll) skulle vara att klienten helt enkelt tilldelar båda rollerna till användare som behöver dem. Naturligtvis är den andra lösningen mer praktisk och ger kunden möjlighet att anpassa systemet till sina behov snabbare (vilket inte stöds av denna modell).

Å andra sidan har denna modell också en stor fördel gentemot andra. Det är enkelt och därför skulle frågor om att ändra status och roller också vara enkla. Dessutom är en fråga som kontrollerar om användaren har rättigheter att logga in på systemet mycket enklare än i andra fall:

select user_account.id, user_account.role_id
from user_account
left join status on user_account.status_id = status.id
where status.is_user_working = True
and user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password;

@user_name och @password är variabler från ett inmatningsformulär medan frågan returnerar användarens id och roll_id som han har. I fall då användarnamn eller lösenord inte är giltiga, paret användarnamn och lösenord inte existerar, eller om användaren har en tilldelad status som inte är aktiv, kommer frågan inte att returnera några resultat. På så sätt kan vi förbjuda inloggning.

Denna modell kan användas i fall då:

  • vi är säkra på att det inte skulle ske några förändringar i processen som kräver att användare har mer än en roll
  • vi behöver inte spåra roller/statusändringar i historiken
  • vi förväntar oss inte att ha mycket roll-/statusadministration.

Tidskomponent tillagd

Om vi ​​behöver spåra en användares roll- och statushistorik måste vi lägga till många till många relationer mellan user_account och role och user_account och status . Självklart tar vi bort role_id och status_id från user_account tabell. Nya tabeller i modellen är user_has_role och user_has_status och alla fält i dem, utom sluttider, är obligatoriska.




Tabellen user_has_role innehåller data om alla roller som användare någonsin haft i systemet. Den alternativa nyckeln är (user_account_id , role_id , role_start_time ) eftersom det inte är någon idé att tilldela samma roll samtidigt till en användare mer än en gång.

Tabellen user_has_status innehåller data om alla statusar som användare någonsin haft i systemet. Den alternativa nyckeln här är (user_account_id , status_start_time ) eftersom en användare inte kan ha två statusar som börjar på exakt samma tid.

Starttiden kan inte vara null eftersom när vi infogar en ny roll/status vet vi från vilket ögonblick den kommer att börja. Sluttiden kan vara noll om vi inte vet när rollen/statusen skulle upphöra (t.ex. rollen är giltig från imorgon tills något händer i framtiden).

Förutom att ha en komplett historik kan vi nu lägga till statusar och roller i framtiden. Men detta skapar komplikationer eftersom vi måste leta efter överlappning när vi infogar eller uppdaterar.

Användare kan till exempel bara ha en status åt gången. Innan vi infogar en ny status måste vi jämföra start- och sluttid för en ny status med alla befintliga statusar för den användaren i databasen. Vi kan använda en fråga som denna:

select *
from user_has_status
where user_has_status.user_account_id = @user_account_id
and 
(
# test if @start_time included in interval of some previous status
(user_has_status.status_start_time <= @start_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= @start_time)
or
# test if @end_time included in interval of some previous status  
(user_has_status.status_start_time <= @end_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= ifnull(@end_time, "2199-12-31"))  
or  
# if @end_time is null we cannot have any statuses after @start_time
(@end_time is null and user_has_status.status_start_time >= @start_time)  
or
# new status "includes" old satus (@start_time <= user_has_status.status_start_time <= @end_time)
(user_has_status.status_start_time >= @start_time and user_has_status.status_start_time <= ifnull(@end_time, "2199-12-31"))  
)

@start_time och @end_time är variabler som innehåller start- och sluttid för en status som vi vill infoga och @user_account_id är det användar-id som vi infogar det för. @end_time kan vara null och vi måste hantera det i frågan. För detta ändamål testas nollvärden med ifnull() fungera. Om värdet är null tilldelas ett högt datumvärde (tillräckligt högt för att när någon upptäcker ett fel i frågan kommer vi att vara borta för länge sedan :). Frågan kontrollerar alla kombinationer av starttid och sluttid för en ny status jämfört med starttid och sluttid för befintliga statusar. Om frågan returnerar några poster har vi överlappning med befintliga statusar och vi bör förbjuda att infoga den nya statusen. Det skulle också vara trevligt att skapa ett anpassat fel.

Om vi ​​vill kontrollera listan över aktuella roller och statuser (användarrättigheter) testar vi helt enkelt med starttid och sluttid.

select user_account.id, user_has_role.id
from user_account
left join user_has_role on user_has_role.user_account_id = user_account.id
left join user_has_status on user_account.id = user_has_status.user_account_id
left join status on user_has_status.status_id = status.id
where user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password
and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time
and user_has_status.status_start_time <= @time and ifnull(user_has_status.status_end_time,"2200-01-01") >= @time
and status.is_user_working = True

@user_name och @password är variabler från inmatningsformuläret medan @time kan ställas in på Now(). När en användare försöker logga in vill vi kontrollera hans rättigheter vid den tidpunkten. Resultatet är en lista över alla roller som en användare har i systemet om användarnamn och lösenord matchar och användaren för närvarande har en aktiv status. Om användaren har en aktiv status men inga roller är tilldelade kommer frågan inte att returnera något.

Den här frågan är enklare än den i avsnitt 3 och den här modellen gör det möjligt för oss att ha en historia av status och roller. Dessutom kan vi hantera statusar och roller för framtiden och allt kommer att fungera bra.

Slutlig modell

Detta är bara en idé om hur den tidigare modellen skulle kunna ändras om vi ville förbättra prestandan. Eftersom en användare bara kan ha en aktiv status åt gången kan vi lägga till status_id till user_account tabell (current_status_id ). På så sätt kan vi testa värdet på det attributet och behöver inte gå med i user_has_status tabell. Den ändrade frågan skulle se ut så här:

select user_account.id, user_has_role.id
from user_account
left join user_has_role on user_has_role.user_account_id = user_account.id
left join status on user_account.current_status_id = status.id
where user_account.user_name = @user_name
and user_account.password_hash_algorithm = @password
and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time
and status.is_user_working = True




Uppenbarligen förenklar detta frågan och leder till bättre prestanda men det finns ett större problem som skulle behöva lösas. current_status_id i user_account Tabellen bör kontrolleras och ändras vid behov i följande situationer:

  • vid varje infogning/uppdatering/borttagning i user_has_status tabell
  • varje dag i en schemalagd händelse bör vi kontrollera om någons status ändrats (aktuell aktiv status har löpt ut eller/och någon framtida status blev aktiv) och uppdatera den därefter

Det skulle vara klokt att spara värden som frågor kommer att använda ofta. På så sätt undviker vi att göra samma kontroller om och om igen och dela jobbet. Här undviker vi att gå med i user_has_status tabell och vi gör ändringar på current_status_id endast när de inträffar (infoga/uppdatera/ta bort) eller när systemet inte används så mycket (schemalagda händelser körs vanligtvis när de flesta användare inte använder systemet). Kanske skulle vi i det här fallet inte tjäna mycket på current_status_id men se på detta som en idé som kan hjälpa i liknande situationer.


  1. django.db.utils.OperationalError Kunde inte ansluta till servern

  2. Oracle-markör för tilldelning

  3. Hur man förhindrar anslutningstidsgränser för stora MySQL-importer

  4. Fulltextsökning i Postgres eller CouchDB?