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


    目录

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

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

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

  • 相关阅读:
    毕业设计
    毕业设计
    毕业设计
    毕业设计
    layui table
    毕业设计
    Echart图标统计
    Pxe自动化安装
    Linux运维常用脚本整理
    Zabbix 一键部署
  • 原文地址:https://www.cnblogs.com/demon28/p/13560068.html
Copyright © 2020-2023  润新知