手把手擼套框架-許可權系統設計

Near_wen發表於2020-08-25

目錄

 

時間又過了一個月,終於熬過了試用期。 之前每天抽時間寫完了程式碼生成器,算是為自己打下了一個不錯基礎。終於熬過了第二個專案。

但是我經常也會陷入各種迷思,現在各種技術都在換代,經常讓我自我懷疑,

後端:.net 從framwork 往 core 轉,

前端:Jquery+Bootstarp  往 Vue+Element 轉

資料操作也 從原來的 寫Sql 往  ORM框架轉,

對我來說,本身就有三四年的 編碼空白期,經常會恐懼要不要使用各種新東西,但是用上去的話,公司又沒人能指導,出了問題也沒人能幫。

所以,對我來說總結一條,對於技術選型儘快可能遵守 “通用技術

比如 Vue,無論java,php,.net 都是通用的, 所以我在框架上 基本上 不用任何 Razor模板,包括最近出的那個Blazor。

這種出了問題,比較百度上的內容也能多點。。。

 

好吧,廢話不多說了,進入今天的主題,許可權系統設計。

想想上次做許可權,都是12年前,讀書時候的事情了,出來工作以後就沒碰過這一塊,剛工作頭兩年專案中都沒有這個模組,小公司就這樣。

後幾年有技術大牛搞定了,而且過去幾年都依賴winner框架有獨立的許可權系統,所以壓根沒想過這一塊。

這不,我一上來第一反應就是要做一個 獨立的許可權系統 結果根本行不通,這和我現在任職的共i是有關係,現在這個公司 是一個工廠型企業,

雖然開發的是內部系統,比如銷售的售後管理系統,文件管理系統的, 乍一看可以做一套  集中管理的許可權,幸好沒這麼幹,公司雖然是一個工廠型企業,

但也是個集團公司,下屬好幾個子公司,每個公司都有銷售,每個共公司 都要這個售後系統,和 文件管理系統,根本 不是我之前那種網際網路企業的 平臺型專案。

說白了,就是要那種小型的獨立的內容管理系統。。所以要的就是內嵌許可權管理。

 

這對我來說更好,想想讀書那時候 那一套許可權設計,十多年了依然適用。五張表許可權設計:

 

 

 

 

 

 

 

簡單明瞭,再做一個 檢視,將這些全部串聯起來,配合 .net 過濾器,起來去還是比較舒服的。

 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Victory.Template.DataAccess.CodeGenerator;
using Victory.Template.Entity.CodeGenerator;
using Victory.Template.Entity.Enums;
using Victory.Template.Entity;

namespace Victory.Template.WebApp.Attribute
{
    public class RightAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 忽略許可權
        /// </summary>
        public bool Ignore { get; set; }

        /// <summary>
        /// 許可權名稱
        /// </summary>
        public string PowerName { get; set; }


        public override void OnActionExecuting(ActionExecutingContext Context)
        {
            base.OnActionExecuting(Context);


            //先取出登入使用者id
            int userid = int.Parse(Context.HttpContext.User.FindFirst("userId").Value);


            //根據配置檔案決定是否給初次登入的使用者 分配一個預設的登入角色
            
            if (AppConfig.IsSetDefautlRole)
            {
                SetDefaultRole(userid);

            }


            //如果Ignore 為true 則表示不檢查該操作,這裡只給他初次登入分配 普通會員角色
            if (Ignore)
            {
                return;
            }


            //獲取路由地址

            string areaName = string.Empty;
            string controllerName = string.Empty;
            string actionName = string.Empty;

            string page = GetPageUrl(Context, ref areaName, ref controllerName, ref actionName);



            //判斷請求的 為訪問頁面 還是 請求功能操作 Ajax請求為功能, 非ajax請求為訪問頁面
            var isAjax = Context.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";


            //判斷資料庫是否存在該許可權,不存則自動新增,無需手動配置
            AddActionFunc(controllerName, actionName, areaName, page, isAjax);


            //如果全域性配置忽略許可權,則忽略檢測
            if (AppConfig.IgnoreAuthRight)
            {
                return;
            }


            //若該使用者存在該頁面許可權,則直接return
            Tright_User_Role_Da userrole = new Tright_User_Role_Da();
            if (userrole.ListByVm(userid, page).Count() > 0)
            {
                return;
            }


            //是否ajax請求,是ajax 則判定為 請求操作, 非ajax則判定為 訪問頁面
            if (isAjax)
            {

                Context.Result = new JsonResult(new { Success = false, Code = 405, Message = "您沒有該功能操作許可權!" });
                return;

            }

            //跳轉指定的沒有許可權的頁面
            Context.Result = new RedirectToRouteResult(new RouteValueDictionary(new
            {
                controller = "UserRight",
                action = "NoPermission"
            })); 
            
            return;

        }


        /// <summary>
        /// 給使用者設定預設登入角色
        /// </summary>
        /// <returns></returns>

        public void SetDefaultRole(int userid) {

            Tright_User_Role_Da userrole = new Tright_User_Role_Da();

            if (userrole.Where(s => s.Userid == userid).Count() <= 0)
            {
                Tright_User_Role userolemodel = new Tright_User_Role()
                {
                    Roleid = 1,   //預設1為普通會員
                    Userid = userid
                };

                userrole.Insert(userolemodel);
            }

        }

        /// <summary>
        /// 獲取當前頁面 或 功能 的路由地址
        /// </summary>
        /// <param name="Context"></param>
        /// <returns></returns>
        public string GetPageUrl(ActionExecutingContext Context, ref string areaName,ref string controllerName, ref string actionName) {


            if (Context.ActionDescriptor.RouteValues.ContainsKey("area"))
            {
                areaName = Context.ActionDescriptor.RouteValues["area"].ToString();
            }
            if (Context.ActionDescriptor.RouteValues.ContainsKey("controller"))
            {
                controllerName = Context.ActionDescriptor.RouteValues["controller"].ToString();
            }
            if (Context.ActionDescriptor.RouteValues.ContainsKey("action"))
            {
                actionName = Context.ActionDescriptor.RouteValues["action"].ToString();
            }



            var page = "/" + controllerName + "/" + actionName;

            if (!string.IsNullOrEmpty(areaName))
            {
                page = "/" + areaName + page;
            }

            return page;

        }


        /// <summary>
        /// 根據Action自動新增功能
        /// </summary>
        /// <returns></returns>
        public void AddActionFunc(string controllerName,string actionName,string areaName,string page,bool isAjax)
        {


            //資料庫是否存在該頁面配置
            Tright_Power_Da pwmanager = new Tright_Power_Da();
            bool HasPage = pwmanager.Where(s => s.Pageurl.ToLower() == page.ToLower()).Count() <= 0;


            if (HasPage)
            {

                Tright_Power powermodel = new Tright_Power
                {
                    Controller = controllerName,
                    Action = actionName,
                    Area = areaName,
                    Powername = PowerName,
                    Pageurl = page.ToLower()
                };

                if (isAjax)
                {
                    // 新增一個功能功能操作的許可權
                    var m = pwmanager.Where(s => s.Controller == controllerName && s.Powertype == (int)PowerType.頁面訪問).First();

                    powermodel.Parentid = m.Id;
                    powermodel.Powertype = (int)PowerType.功能操作;

                }
                else
                {
                    //新增一個 頁面訪問 許可權
                    powermodel.Parentid = 0;
                    powermodel.Powertype = (int)PowerType.頁面訪問;

                }

                pwmanager.Insert(powermodel);

            }



        }

    }
}

  

 使用期起來也特別方便,打個特性類就型:

 

  [Right(PowerName = "人員資訊")]
        public IActionResult Index()
        {
            return View();
        }

 

上效果圖:

 

 

 

 

 

 

但是,我的第二個專案是個文件管理系統,有個要求,要求某些檔案某些人能看,某些人不能看,這套許可權就完全做不到了,而且像我們公司這樣的企業。

還涉及到有些檔案,某些部門的人能看,有些部門的不能看。 說 白了 就是 五張表的這種許可權設計, 有兩個問題:

 

1,許可權 不能控制檔案。

2,沒有使用者組。

 

別看我從事網際網路十年,以前用的許可權,還真沒有涉及這兩塊,只是知道有使用者組許可權,但是以前做的專案,完全都沒涉及到這一塊。

這不,到處百度,Github上下了幾個專案看了看, 感覺都挺扯淡的, 總之沒看到一個符合我上面那兩個需求的,多數一想,應該是我百度的方式不對。。。

 

有一天中午跟同事無意聊起這個話題,同事跟我說了一個詞語 “RBAC” “ACL”  瞬間表示 不懂,  回來百度有一下。。。呀···! 原來我前面那種五張表設計

也屬於 RBAC,原來還專門有個這名詞,和一套理論體系。。。  翻了翻,芭拉 巴拉。。。反正沒看特別懂,主要是現在心態越來越浮躁了,真的有那種三十歲以後學習能力跟不上的感覺。

雖然沒看特別懂,但是知道。。這就是我想要的。不管了。直接上手畫表圖吧。

 

 

 

參考資料:https://www.cnblogs.com/jpfss/p/11210694.html

 

這裡,由於業務各有不同,所以 我這裡有些表精簡了欄位,值得一提的是,我也有看到 有些表設計 使用者組 表 Tright_Group 那裡 並沒設計Parent_ID ,也就是說使用者組 (部門)沒有層級關係。

有的 角色表 Tright_Role 有Parent_ID, 大概意思是 角色 可以繼承。  無論是 角色可以繼承 還是 使用者組 可以繼承 都是標識,許可權可以繼承。 這個我沒有去深究,反正。我現在任職的這個功能

很麻煩, 五級部門。所以 使用者組 那裡 是一定要設計 Parent_ID 的。

 

這裡資料庫我用的 Sqlserver,(其實,我更熟悉oracle) 這裡貼一下建表的sql:

 

 

CREATE TABLE [Tright_File] (
  [Id] int NOT NULL,
  [File_Name] varchar(255) NULL,
  [File_Url] varchar(255) NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_2_copy_1_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'檔案表'
GO

CREATE TABLE [Tright_Group] (
  [Id] int NOT NULL,
  [Group_Name] varchar(255) NULL,
  [Parent_Id] int NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_2] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者組'
GO

CREATE TABLE [Tright_Group_Role] (
  [Id] int NOT NULL,
  [Group_Id] int NULL,
  [Role_Id] int NULL,
  CONSTRAINT [_copy_2_copy_2] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者組_角色中間表'
GO

CREATE TABLE [Tright_Menu] (
  [Id] int NOT NULL,
  [Menu_Name] varchar(255) NULL,
  [Menu_Url] varchar(255) NULL,
  [Parent_Id] int NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_2] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Operation] (
  [Id] int NOT NULL,
  [Code] varchar(255) NULL,
  [Area] varchar(255) NULL,
  [Controller] varchar(255) NULL,
  [Action] varchar(255) NULL,
  [Url] varchar(255) NULL,
  [SortId] int NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_2_copy_1_copy_1_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'操作表'
GO

CREATE TABLE [Tright_PageElement] (
  [Id] int NOT NULL,
  [Element_Name] varchar(255) NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_2_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'狀態'
GO

CREATE TABLE [Tright_Power] (
  [Id] int NOT NULL,
  [Power_Type] varchar(255) NULL,
  CONSTRAINT [_copy_1_copy_2] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Power_Element] (
  [Id] int NOT NULL,
  [Page_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_1_copy_1_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Power_File] (
  [Id] int NOT NULL,
  [File_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_1_copy_1_copy_2] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Power_Menu] (
  [Id] int NOT NULL,
  [Menu_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_1_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Power_Opeartion] (
  [Id] int NOT NULL,
  [Operation_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_1_copy_1_copy_2_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Role] (
  [Id] int NOT NULL,
  [RoleName] varchar(255) NULL,
  [Status] int NULL,
  CONSTRAINT [_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_Role_Powe] (
  [Id] int NOT NULL,
  [Role_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [_copy_1_copy_2_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO

CREATE TABLE [Tright_User_Group] (
  [Id] int NOT NULL,
  [User_Id] int NULL,
  [Group_Id] int NULL,
  CONSTRAINT [_copy_2_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者_使用者組中間表'
GO

CREATE TABLE [Tright_User_Role] (
  [Id] int NOT NULL,
  [User_Id] varchar(255) NULL,
  [Role_Id]  NULL,
  CONSTRAINT [_copy_1_copy_1] PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'角色id'
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者_角色中間表'
GO

CREATE TABLE [Tsys_User] (
  [Id] int NOT NULL,
  [User_Name] varchar(255) NULL,
  [User_Pwd] varchar(255) NULL,
  PRIMARY KEY CLUSTERED ([Id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'使用者表'
GO

ALTER TABLE [Tright_Group_Role] ADD CONSTRAINT [fk_Tright_Group_copy_1_Tright_Group_copy_1_1] FOREIGN KEY ([Group_Id]) REFERENCES [Tright_Group] ([Id])
GO
ALTER TABLE [Tright_Group_Role] ADD CONSTRAINT [fk_Tright_Group_Role_Tright_Group_Role_1] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO
ALTER TABLE [Tright_Power_Element] ADD CONSTRAINT [fk_Tright_Power_Element_Tright_Power_Element_1] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_Element] ADD CONSTRAINT [fk_Tright_Power_Element_Tright_Power_Element_2] FOREIGN KEY ([Page_Id]) REFERENCES [Tright_PageElement] ([Id])
GO
ALTER TABLE [Tright_Power_File] ADD CONSTRAINT [fk_Tright_Power_File_Tright_Power_File_1] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_File] ADD CONSTRAINT [fk_Tright_Power_File_Tright_Power_File_2] FOREIGN KEY ([File_Id]) REFERENCES [Tright_File] ([Id])
GO
ALTER TABLE [Tright_Power_Menu] ADD CONSTRAINT [fk_Tright_Power_Menu_Tright_Power_Menu_1] FOREIGN KEY ([Menu_Id]) REFERENCES [Tright_Menu] ([Id])
GO
ALTER TABLE [Tright_Power_Menu] ADD CONSTRAINT [fk_Tright_Power_Menu_Tright_Power_Menu_2] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_Opeartion] ADD CONSTRAINT [fk_Tright_Power_Opeartion_Tright_Power_Opeartion_1] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_Opeartion] ADD CONSTRAINT [fk_Tright_Power_Opeartion_Tright_Power_Opeartion_2] FOREIGN KEY ([Operation_Id]) REFERENCES [Tright_Operation] ([Id])
GO
ALTER TABLE [Tright_Role_Powe] ADD CONSTRAINT [fk_Tright_Role_Powe_Tright_Role_Powe_1] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO
ALTER TABLE [Tright_Role_Powe] ADD CONSTRAINT [fk_Tright_Role_Powe_Tright_Role_Powe_2] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_User_Group] ADD CONSTRAINT [fk_Tright_User_Group_Tright_User_Group_1] FOREIGN KEY ([User_Id]) REFERENCES [Tsys_User] ([Id])
GO
ALTER TABLE [Tright_User_Group] ADD CONSTRAINT [fk_Tright_User_Group_Tright_User_Group_2] FOREIGN KEY ([Group_Id]) REFERENCES [Tright_Group] ([Id])
GO
ALTER TABLE [Tright_User_Role] ADD CONSTRAINT [fk_Tright_User_Role_Tright_User_Role_1] FOREIGN KEY ([User_Id]) REFERENCES [Tsys_User] ([Id])
GO
ALTER TABLE [Tright_User_Role] ADD CONSTRAINT [fk_Tright_User_Role_Tright_User_Role_2] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO

 

 

SQL 是由 設計工具生成的,所以外來鍵命名 有點亂。我也沒心思去改了,我是直接刪掉了,現在建資料庫,我基本都不建外來鍵了。。。。

 

其實一套小型框架,主要就是 這麼幾件事,登入,許可權管理,系統日誌,。剩下的都可以用開源的工具去組裝,比如ORM用FreeSql,用log4net 去寫日誌,NPOI做匯入匯出。  前端要不Element UI 要不就 Bootstarp框架。

關鍵是 把技術定型。 不去東試試,西試試。   定型下來之後 就可以專心關注  核心業務。 另外,抽出來的框架部分,也可以持續更新去做  有   積累的開發。。

 

先寫到這裡 ,其實前端,分層框架 也做完了,但是隨著這次許可權升級,也會做一次更新。下次放出來,具體自己說的6個擼套框架,其實最近轉正之後 ,整個人鬆懈很多。。還是得繼續,畢竟自己的人生規劃就是未來三年

就在這種企業,先把創業失敗欠的錢先還清。。。 35歲之後再出發吧··!!!

 

相關文章