時間又過了一個月,終於熬過了試用期。 之前每天抽時間寫完了代碼生成器,算是為自己打下了一個不錯基礎。終於熬過了第二個項目。
但是我經常也會陷入各種迷思,現在各種技術都在換代,經常讓我自我懷疑,
后端:.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歲之后再出發吧··!!!
