zoukankan      html  css  js  c++  java
  • L型代码结构案例:Link访问权限(上)

    这是松结对编程的第20篇(专栏目录)。

    本文探讨Link访问权限的最佳实现方法,力求外观干净且封装良好。

    这些代码将位于L型代码结构(参见松结对编程系列中的定义)的下层,调用者无需理解其原理。

    顺便说一下,我们做的是管理信息系统,和互联网社区软件的一个区别是很多链接都是需要特定权限才能访问的,有些权限也不是非常直观能猜到应该具备何种条件才能访问,另外一些权限还会经常改动。因此一个容易使用、容易维护、不容易出错的权限机制尤为重要。

    无权访问时该显示什么

    在说实现方法之前,先说说如果链接访问条件不满足,应该显示什么。

    实践发现显示文字不好,因为文字(这里应该是一段解释为何不能访问的文字)肯定比链接长,显示空间不好。

    什么都不显示如何?也不太好,用户可能会误以为没有这样一个功能,或链接不在这个页面上而去其他页面寻找。

    最后现在是显示一个灰色的链接,悬停时解释需要什么条件才能访问这个链接(这样用户如果想操作它但却没有权限,就会知道该怎么办)。

    显示方法

    方法1:散装代码

    一般而言,如果要限制一个Link的访问权限,都是这样的:

    if (condiction)
    {
            @link
    }
    else
    {
            @text //灰色代码,或干脆什么都不显示。
    }
    

    这样的最大坏处是,如果一个页面上有很多链接(比如导航页面),那么遍地都是if-else,眼花缭乱。

    而且一旦权限修改了,就要到处修改所有可能引用过的地方。

    方法2:封装Link

    下面是我们原来封装的Link,里边有两个参数:
    displayAsLink,即何种条件下应该显示为Link,否则将显示为灰色文字。比如product.IsProductManager()是问当前用户(括号内无值时自动采用当前用户)是否是产品经理。是,才显示链接。
    grayTextTitle,显示为灰色文字时,以悬停文字解释为何不能访问。
    注意还有一个displayAsBoardTextOnPage:this(this是当前Page)是问当前Page是否就是链接所在地,如果是就显示为黑体文字而不在显示连接了。
            @MFCUI.ImageLink("只读树", "/ProductManagement/StoryTrees/IndexTree?" + Request.QueryString,
                displayAsBoldTextOnPage: this, title: "只读故事树,速度较快")
             
            @MFCUI.ImageLink("操作树",
                "/ProductManagement/StoryTrees/OperateTree?" + Request.QueryString,
                displayAsBoldTextOnPage: this, title: "可拖拽和执行所有操作,速度较慢",
                displayAsLink: product.IsProductManager(),
                grayTextTitle: "需要是此产品的产品经理才能操作。")
             
            @MFCUI.ImageLink("详情树",
                "/ProductManagement/StoryTrees/DetailsTree?" + Request.QueryString,
                displayAsBoldTextOnPage: this, title: "适合快速查看所有故事的详情")
             
            @MFCUI.ImageLink("编辑树",
                "/ProductManagement/StoryTrees/EditTree?" + Request.QueryString,
                displayAsBoldTextOnPage: this, title: "适合连续编辑多个故事的数据",
                displayAsLink: product.IsProductManager(),
                grayTextTitle: "需要是此产品的产品经理才能操作。")
             
    
    这个方法解决了前面提到的if-else满天飞的问题,但是要解决缺陷更改造成的代码改动还不行。
    此外,一些解释性的语言如“需要时此产品的产品经理才能操作”也存在多处文字的统一问题;甚至即使在同一地方,如果displayAsLink修改了条件,而grayTextTitle没有相应修改,会造成用户的错误理解。

    方法3:封装模型

    其实上面四句话中,能访问或不能访问,都是关于product的写操作权限的,那么如果用product自己来判断,那么代码就会集中在product内部,由专业维护此类的人员来确定权限和解释。
    这个是现在为止最佳的选择。
    当前View调用处的代码如下:
            @product.IndexTreeLink(this)   
            @product.OperateTreeLink(this)   
            @product.DetailsTreeLink(this)   
            @product.EditTreeLink(this)   
    
    Product类中代码如下:
        public partial class Product : Item
        {
            public MvcHtmlString IndexTreeLink(WebViewPage page)
            {
                var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;
                return MFCUI.ImageLink("只读故事树",
                    "/ProductManagement/StoryTrees/IndexTree?" + queryString,
                    displayAsBoldTextOnPage: page, title: "只读故事树,速度较快");
            }
    
            public MvcHtmlString OperateTreeLink(WebViewPage page)
            {
                var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;
                return MFCUI.ImageLink("操作故事树",
                    "/ProductManagement/StoryTrees/OperateTree?" + queryString,
                    displayAsBoldTextOnPage: page, title: "可拖拽和执行所有操作,速度较慢",
                    displayAsLink: IsProductManager(),
                    grayTextTitle: "需要是此产品的产品经理才能操作。");
    
            }
    
            public MvcHtmlString DetailsTreeLink(WebViewPage page)
            {
                var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;
                return MFCUI.ImageLink("详情故事树",
                    "/ProductManagement/StoryTrees/DetailsTree?" + queryString,
                    displayAsBoldTextOnPage: page, title: "适合快速查看所有故事的详情");
            }
    
            public MvcHtmlString EditTreeLink(WebViewPage page)
            {
                var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;
                return MFCUI.ImageLink("编辑故事树",
                    "/ProductManagement/StoryTrees/EditTree?" + queryString,
                    displayAsBoldTextOnPage: page, title: "适合连续编辑多个故事的数据",
                    displayAsLink: IsProductManager(),
                    grayTextTitle: "需要是此产品的产品经理才能操作。");
            }
        }
    

    刚才查找替换了一下,每个链接都出现过6次。通过改写后,原来有很多可能导致不一致的参数的调用都变成一个只传输(this)的参数了,未来维护会简单地多。

    下面是一个具体的实现效果:

    方法4:重写MVC的Authorize属性

    方法3虽然很好,但是只是隐藏了链接而已,真正要访问,手工输入链接仍然可以。
    asp.net 为我们封装了一个Authorize的属性,可以这样来简单阻止非法访问(第一行代码):
            [Authorize(Roles = "ProductManager")]
            public ActionResult OperateTree(int rootID, string whats, string whattypes)
            {
                ...
    
                var root = _repository.ReadItemAt(rootID);
                var product = root is Product ? root as Product : root.YoungestAncesstor<Product>();
                if (!product.IsProductManager())
                    return Content("只有此产品的产品经理才可以操作。");
    
                return OperateTreeView(...);
            }
    
    可惜有这么几个限制:
    1. 只提供Users(常量的用户名,基本没什么用)和Roles(上例中的)两种。
    比如上例中“此产品的产品经理”,就只能编码实现。
    2. (似乎)没有地方查看某个Action具体访问权限是什么
    也就是说,不能在别处生成指向此Action的链接时,自动根据所限定的Users或Roles来自动决定显示为链接或文字。
    重写Authorize可以根据自己的条件来限制访问,唯一的问题是“自己的条件”如果太多,那么会很复杂。

    还好,到现在为止火星人中就用了两种限制:
    1. 某人是某个角色。
    比如SiteUsersController只有Admin才可以访问,这个是站点的用户管理功能。
    2. 某人是某物的某个角色。
    现在火星人中,“某物”包括产品(的产品经理)、团队(的项目经理、项目助理经理即项目经理不在时应急用的代理人、项目组员)、用户故事(的负责人、当前负责人、创建者)、缺陷(的创建人、当前负责人)……,未来还有部门(的经理、部门助理经理、部门人员)……这些。
    这些虽然听起来很多,但是还好之前为了存储问题,产品、团队、用户故事、缺陷……这些都是从基类UDCable(User Defined Column-able,“可被用户自定义字段的”)派生的,而刚才说的一大堆角色,都是一个个UDCType(User Defined Column Type,“用户自定义字段类型”),这样其实所有刚才说的判断,都是一种,就是问某人的Id是否等于某个UDCable的某个UDCType字段数值。

    现在还没时间写这个Authroize(主要是不会写,呵呵),估计写好后使用方法如下:
            [Authorize(Roles = "ProductManager", UDCRoles="rootID, ProductManager")]
            public ActionResult EditTree(int rootID, string whats, string whattypes)
            {
                ...
                return OperateTreeView(...);
            }
    

    UDCRoles="rootID, ProductManager"是说,用url的rootID数值来找UDCable,然后判断其"ProductManager"是否等于当前用户。

    用这个属性后Action中可以减少3行(一共才5行,所以3行很多了),而整个代码中有很多这样的三行代码,估计现在有30处左右,都很容易写错造成漏洞。

    剩下一个问题,@MFCUI.ImageLink怎么知道这些Action的访问权限呢?

    现在的想法是在属性代码中用"Area/Controller/Action"作为Key,权限设置(就是“rootID, ProductManager”)作为值做一个静态缓存,ImageLink会根据自己传入的Url解析出“Area/Controller/Action”并去查找缓存的值,如果找到就根据权限进行判断是否显示为链接)。这样未来只要在Action前面写好属性,所有指向它的链接都会自动判断。

    因为之前已经有很多可用的代码了(比如解析A/C/T的代码),所以估计两者加起来大约有10~15行代码就能实现。

    估计这些代码两个月后才会排到足够优先级,写好了我共享一下。

    总结

    所有代码结构中的第一块积木是最难的,如果不是我们原来有一些复用了,上面这个访问权限问题解决起来可能需要上百行代码,很容易将就一下就硬编码过去了。

    如果我们当年所有View里边的链接都是用<a></a>硬编码的,或用MVC中自带的Hmtl.Link()写的,那么我们也没有勇气和动力来用“这么复杂”的方式来解决这个访问权限问题了。但若干时间后,一旦访问权限变化了,肯定会因为各地的硬编码而出现无数问题,那时候就真的乱了。

    UDCable和ImageLink这些都是接近一年半年前产生的,那时候完全没想到会与现在要做的权限控制相关。只能说,如果做对了事情,回报是迟早的。

    所以应该随时随地把可复用的东西总结起来,这样反而不觉得累,才能不断在原来的基础上前进。

  • 相关阅读:
    C#获取配置文件中的文件数据
    wpf MVVMLight的DataGrid绑定数据
    扫码支付自动跳转,可以使用第三方网站实现扫码二维码付款然后跳转到想要的页面展示想要内容或者是解压码或者是某个资源的下载页呢 具体步骤(我以你上传一个压缩包到某种网盘或者可以下载的地址等让人付费解压为例):
    oracle数据库如何创建用户以及分配权限
    ORA-12547: TNS: 丢失连接
    springmvc中applicationapplicationContext头部代码
    No mapping found for HTTP request with URI
    在Navicat新建用户
    myeclipse 项目引入 com.sun.image.codec.jpeg 的api报错解决方法
    java.lang.NullPointerException at org.apache.jsp.**_jsp.jspInit(**_jsp.java)tomcat启动异常解决方法
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3324929.html
Copyright © 2011-2022 走看看