这是松结对编程的第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这些都是接近一年半年前产生的,那时候完全没想到会与现在要做的权限控制相关。只能说,如果做对了事情,回报是迟早的。

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

L型代码结构案例:Link访问权限(上)的更多相关文章

  1. 敏捷开发松结对编程系列:L型代码结构案例StatusFiltersDropdownList(中)

    这是松结对编程的第22篇(专栏目录). 接前文 业务代码 比较长,基本上就是看被注释隔开的三大段,先显示状态群筛选链接,然后是单个状态筛选,然后是显示下拉框的当前选中项,最后显示下拉框. public ...

  2. 3.GO-项目结构、包访问权限、闭包和值传递引用传递

    3.1.goland中项目结构 (1)在goland中创建标准Go项目 (2)goland配置 创建项目Learn-Go file-settings-go-GOPATH-添加 在项目目录下创建src目 ...

  3. go 基础 结构体 接口 访问权限

    package School type SchoolModel struct { Name string Address string StudentCount int Is985 bool } ty ...

  4. 初读"Thinking in Java"读书笔记之第六章 --- 访问权限控制

    包:库单元 包内包含有一组类,他们在单一的名字空间下被组织在一起. 通过import ***.***.*可以将某个包下的所有类导入到当前文件中. 每个Java源文件最多只能有一个public类,且名称 ...

  5. Linux中ls -l(ll)返回结果中的文件访问权限-rw-r--rw-

    linux文件访问权限(像rw-r--rw-是什么意思)   Linux的文件访问权限分为 读.写.执行三种 r:可读(4) w:可写(2)对目录来说则可新建文件 x:可执行(1)对目录来说则可进入该 ...

  6. SYS_数据访问权限Operation Unit和Ledger的访问设定(案例)

    2014-06-01 Created By BaoXinjian

  7. [apue] linux 文件访问权限那些事儿

    前言 说到 linux 上的文件权限,其实我们在说两个实体,一是文件,二是进程.一个进程能不能访问一个文件,其实由三部分内容决定: 文件的所有者.所在的组: 文件对所有者.组用户.其它用户设置的权限访 ...

  8. CentOS 6.5系统上安装SVN服务器端的方法及目录访问权限配置(转总结)

    SVN其实就是Subversion,分为服务器端和客户端.之前在网上搜了很多方法,都有各种问题,经过自己搜集整理以及实际尝试,总算有个比较靠谱的方法.本文主要介绍CentOS 6.5系统上安装SVN服 ...

  9. Unix/Linux文件类型及访问权限

    在Linux系统中,有7种文件类型. 普通文件 (regular file) 目录文件 (directory) 链接文件 (symbolic link) 管道文件 (FIFO) 套接字文件 (sock ...

随机推荐

  1. 使用Xib添加自定义View

    1.新建Cocoa Touch Class以及UI View,2者同名 2.设置UI View的File's Owner——Custom Class为之前新建类 3.设置Xib中View与类关联 4. ...

  2. BZOJ 1316: 树上的询问( 点分治 + 平衡树 )

    直接点分治, 用平衡树(set就行了...)维护. -------------------------------------------------------------------------- ...

  3. 手动添加删除windows服务

    1.使用sc命令创建服务 命令格式如: sc create [service name] [binPath= ] <option1> <option2>... 比如: sc c ...

  4. Eclipse4.3正式版已发布

    Eclipse4.3正式版已发布,传送门http://www.eclipse.org/downloads/

  5. gdal 1.9+python 2.7开发环境配置

    最近项目使用Cesium平台基于WegGl做web地球,其中关于地形数据有一种支持格式为terrain的地形数据.这种格式可以通过一个python工具切dem来得到. 下面记录下配置gdal+pyth ...

  6. IOS 表视图(UITableVIew)的使用方法(2)名单的分段显示

    我们可以采用名字分段法,名字分段会在之后的小节中显示,这是转而使用球员的角色分段发,以最直接的入手点讲解好UITableView的分段使用方法.本节示例时基于上节的SimpleTableViewCon ...

  7. 【转】android windowSoftInputMode

    android:windowSoftInputMode activity主窗口与软键盘的交互模式,可以用来避免输入法面板遮挡问题,Android1.5后的一个新特性. 这个属性能影响两件事情: [一] ...

  8. Delphi 常用属性说明(超长)

    Delphi组件的常用事件Onclick——当单击时触发这个事件中的代码Onchange——当改变该组件内容时触发其中的代码Oncreate——当创建时触发这个事件中的代码Onclose——当关闭的时 ...

  9. golang实现udp接入服务器

    前端通过udp与接入服务器连接,接入服务器与后端tcp服务器维持tcp连接.目录结构及后端tcp服务器代码同上一篇博客. main.go package main import ( "lot ...

  10. Linux相关问题-CentOS6.5 x64版本号下Tomcat无法自启动的解决的方法

    前段时间使用阿里云server.使用的是Linux CentOS6.5系统,在搭建完Tomcat后发现,Tomcat无法自启动. 将启动tomcat的命令为tomcat_home/bin/startu ...