时间又过了一个月,终于熬过了试用期。 之前每天抽时间写完了代码生成器,算是为自己打下了一个不错基础。终于熬过了第二个项目。
但是我经常也会陷入各种迷思,现在各种技术都在换代,经常让我自我怀疑,
后端:.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岁之后再出发吧··!!!