手把手撸套框架-权限系统设计

目录

时间又过了一个月,终于熬过了试用期。 之前每天抽时间写完了代码生成器,算是为自己打下了一个不错基础。终于熬过了第二个项目。

但是我经常也会陷入各种迷思,现在各种技术都在换代,经常让我自我怀疑,

后端:.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岁之后再出发吧··!!!

原文地址:https://www.cnblogs.com/demon28/p/13560068.html