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


目錄

 

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

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

后端:.net 從framwork 往 core 轉,

前端:Jquery+Bootstarp  往 Vue+Element 轉

數據操作也 從原來的 寫Sql 往  ORM框架轉,

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

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

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

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

 

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

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

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

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

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

但也是個集團公司,下屬好幾個子公司,每個公司都有銷售,每個公司 都要這個售后系統,和 文檔管理系統,根本 不是我之前那種互聯網企業的 平台型項目。

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

 

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

 

 

 

 

 

 

 

簡單明了,再做一個 視圖,將這些全部串聯起來,配合 .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 [tright_file_id] 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 [pk_tright_group_id] 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 [tright_menu_id] 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 [tright_operation_id] 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_PageElement] (
  [Id] int NOT NULL,
  [Element_Name] varchar(255) NULL,
  [Status] int NULL,
  CONSTRAINT [tright_pageelement_id] 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 [tright_power_id] 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 [tright_power_element_id] 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 [tright_power_file_id] 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 [tright_power_menu_id] 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 [tright_power_opeartion_id] 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 [pk_tright_role_id] 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_Power] (
  [Id] int NOT NULL,
  [Role_Id] int NULL,
  [Power_Id] int NULL,
  CONSTRAINT [tright_role_power_id] 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 [pk_tright_user_group_id] 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 [pk_tright_user_id] 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_Role_Groupid] FOREIGN KEY ([Group_Id]) REFERENCES [Tright_Group] ([Id])
GO
ALTER TABLE [Tright_Group_Role] ADD CONSTRAINT [fk_Tright_Group_Role_Roleid] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO
ALTER TABLE [Tright_Power_Element] ADD CONSTRAINT [fk_Tright_Power_Element_PageId] FOREIGN KEY ([Page_Id]) REFERENCES [Tright_PageElement] ([Id])
GO
ALTER TABLE [Tright_Power_Element] ADD CONSTRAINT [fk_Tright_Power_Element_PowerId] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_File] ADD CONSTRAINT [fk_Tright_Power_File_FileId] FOREIGN KEY ([File_Id]) REFERENCES [Tright_File] ([Id])
GO
ALTER TABLE [Tright_Power_File] ADD CONSTRAINT [fk_Tright_Power_File_PowerId] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_Menu] ADD CONSTRAINT [fk_Tright_Power_Menu_MenuId] FOREIGN KEY ([Menu_Id]) REFERENCES [Tright_Menu] ([Id])
GO
ALTER TABLE [Tright_Power_Menu] ADD CONSTRAINT [fk_Tright_Power_Menu_PowerId] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Power_Opeartion] ADD CONSTRAINT [fk_Tright_Power_Opeartion_OpeartionId] FOREIGN KEY ([Operation_Id]) REFERENCES [Tright_Operation] ([Id])
GO
ALTER TABLE [Tright_Power_Opeartion] ADD CONSTRAINT [fk_Tright_Power_Opeartion_PowerId] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Role_Power] ADD CONSTRAINT [fk_Tright_Role_Powe_Powerid] FOREIGN KEY ([Power_Id]) REFERENCES [Tright_Power] ([Id])
GO
ALTER TABLE [Tright_Role_Power] ADD CONSTRAINT [fk_Tright_Role_Powe_Roleid] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO
ALTER TABLE [Tright_User_Group] ADD CONSTRAINT [fk_Tright_User_Group_Userid] FOREIGN KEY ([User_Id]) REFERENCES [Tsys_User] ([Id])
GO
ALTER TABLE [Tright_User_Group] ADD CONSTRAINT [fk_Tright_User_Group_Groupid] FOREIGN KEY ([Group_Id]) REFERENCES [Tright_Group] ([Id])
GO
ALTER TABLE [Tright_User_Role] ADD CONSTRAINT [fk_Tright_User_Role_Roleid] FOREIGN KEY ([Role_Id]) REFERENCES [Tright_Role] ([Id])
GO
ALTER TABLE [Tright_User_Role] ADD CONSTRAINT [fk_Tright_User_Role_Userid] FOREIGN KEY ([User_Id]) REFERENCES [Tsys_User] ([Id])
GO

 

 

 

 

 

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

 

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

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

 

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM