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:
-
Vi behåller ingen historik (eller framtida) data.
När vi ändrar status eller roll uppdaterar vi helt enkelt
status_id
ochrole_id
iuser_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.
-
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.