net生活

博客园 首页 新随笔 联系 订阅 管理
  30 Posts :: 0 Stories :: 496 Comments :: 26 Trackbacks

系统设计一个很重要的目的就是为了重用﹐而要做到重用﹐低耦合是最有效的手段。

 

本文将通过web应用系统中一个最常见的主题--权限设计﹐来说明解耦的应用。

 

要解耦﹐首先就要进行抽象﹐权限究竟能不能抽象?

 

我认为通常意义上的权限应该分为2类﹕

一类是用户是否有权进行某项动作﹐如管理员可以删贴﹐人事考勤员可以修改考勤数据。这种权限就是最简单的有或无问题﹐毫无疑问﹐这是可以抽象出来单独进行设计的。

 

另一类我将它称为数据权限﹐如某某人可以查看某某部门的人员信息﹐某某人审核某某厂别的订单﹐某某人具有某某报表的下载权限等﹐这种权限与具体的应用系统有关﹐与具体的应用逻辑有关﹐需要在系统分析时解决的﹐并在程序中嵌入这些代码﹐当业务逻辑发生改变时﹐常常需要修改这些代码逻辑。

当然这种权限应用也可以进行抽象﹐但是其耦合程度相对较高﹐抽象后进行的重用是在coding级别的﹐比如封装了权限的分配﹐移除和获取代码﹐但是需要new 一个权限类﹐并在业务系统中使用该权限类。

 

本文着重通过第一类权限的应用的说明来阐述解耦的应用.

第一类权限其实是应用最为广泛的权限﹐对此类权限设计最常见的就是分角色如一个BBS﹐有管理员﹐斑竹﹐普通用户﹐匿名用户4种角色。每个人员是一种角色﹐每种角色可访问的菜单﹐页面或按钮是不一样的。

一些小型的﹐逻辑相对简单﹐应用已稳定的系统使用此种方法可大大减化权限的设计。

 

但是它毕竟是耦合的。

 

举个最简单的例子﹐例如在贴子列表页面上﹐斑竹可能在每个贴子后面会多一个删除按钮﹐而普通用户则不会有此按钮。程序员经常需要这样编码﹕

if(user is 管理员 斑竹)

    删除按钮.visible = true

else

    删除按钮.visible = false

 

乍看这样的代码﹐是看不到有什么问题的。但至少有一点ugly的就是出现了程序员最忌讳的HardCode代码:user is 管理员 斑竹﹐可能有人说这一点点不会有太大影响。

 

但是这至少让我们的系统有了如下的假定﹕

1﹕只有管理员或斑竹才能删除贴子﹐这种假设是很难站住脚的﹐系统不断变化﹐很容易哪天出现了一个"代斑竹""副斑竹"的角色﹐它们也可以删贴...

2﹕系统建立在这几种固定的角色之上﹐相信除了这个页面的判断角色代码外﹐系统的其它地方一定也会充斥这样的代码﹐一旦要改﹐那就只好将此类代码全部找出来...

 

一旦这些假设变化了﹐系统就会被这种耦合拖累

在我几年的程序设计生涯里﹐碰到的这种权限修改要求可谓数不胜数﹐某个主管打电话过来﹐能不能帮我开放一下考勤查询权限﹐我想看到我们部门的考勤状况。天啦﹐考勤查询是考勤员才能查的呀﹐开放这个给他可以呀﹐但更多的只有人事考勤员才能操作的功能都"不安全"的暴露在他面前了﹐谁知道它会不会去点点里面的哪个按钮?

 

你也许会说﹐当初开发程序时﹐需求分析就应该做清楚﹐现在请按正常的系统维护流程来做。

 

不单说他是主管﹐也可能是客户﹐我们就无法拒绝。

更重要最终我们还要是改程序﹐还是要面对哪些散落在系统各个角落的权限判断代码﹐为什么当初我们就不留下一个心眼呢?

 

解决的方法就是将权限抽象﹐与具体的业务系统解耦﹐开发系统的时候完全不去考虑这个系统有哪些角色﹐他们哪些人可以做什么﹐哪些人又可以做什么。而是将这部分需求撇开。

比如我们在编写删除贴子的代码时﹐就完全不用写下

if(has 删除权限)

   //删除贴子代码

 

而是直接就写删除﹐我管你有没有删除权限﹐但你代码既然执行到了这里﹐那我假定你肯定就有删贴子的权限﹐至于你是管理员﹐斑竹﹐副斑竹﹐甚至是普通用户我也不管﹐也许你高兴﹐让某个普通用户也暂时可以删贴子(客户或主管要求嘛)

 

这样我们就把整个系统的功能全部开发出来了。

 

现在﹐任何人进入系统﹐映入它眼帘的就是所有菜单﹐所有按钮﹐太舒服了。

 

好了﹐现在我们再来考虑用户的权限要求吧﹕

用户要求分4种角色﹕管理员﹐斑竹﹐普通用户﹐匿名用户

管理员什么事情都可以做

斑竹只可以删贴

普通用户可以撤斑竹

匿名用户只能进入浏览页面

 

建几个表﹐做一下对应﹐然后写一个方法﹐传入userid和功能﹐返回truefalse就可以了(我以前的贴子有介绍过一种具体的方案)

 

这个权限管控放在哪里呢?

很简单﹐只要不要和你的业务代码放在一起就可以了﹐也许你可以通过AOP这种时髦的方式来完成﹐或者最简单的﹐就写一个类﹐在每次调用方法时﹐统一判断权限。

 

web系统中就最简单了﹐只要做一个访问的url和功能的对应关系﹐然后利用一个httpmodule在每次request时﹐先把url转换成功能﹐然后再调用上述权限判断方法﹐返回true就进﹐false就转向错误页面。

 

可以看出﹐我们的应用系统很干净﹐我们的权限模块也很干净。

 

最后再提一下﹐在这种权限方案做web页面时经常碰到的一种情形﹐就是我刚刚提到的同一个页面可能有不同权限的情形﹐你无论如何都回避不了那行

if(user is 管理员 斑竹)

    删除按钮.visible = true

else

    删除按钮.visible = false

的代码。

 

是这样的

 

但是换过一个角度我们就能理解了﹐我们不把这种权限看作业务逻辑﹐我现在只是在提供功能﹐需要提供2种功能﹐浏览和管理﹐那好﹐我写两支程序。一支show.aspx﹐一支manage.aspx

 

别人会说你有病

 

确实有病﹐明明是差不多完全一样的代码﹐为什么要写两支程序﹐这不找抽吗?

没办法﹐我就写一支程序

postlist.aspx

但是两个功能的访问地址分别是﹕

post.aspx?type=show

post.aspx?type=manage

 

那行代码也巧妙地变为了﹕

if(request["type"]=="manage")

    删除按钮.visible = true

else if(request["type"] == "show")

    删除按钮.visible = false

 

这样你就看到了﹐程序员在写这个页面的代码时﹐完全占据了主动﹐再也不会去问系统分析师﹕老大﹐究竟谁才有删贴子的权限呢?

 

而只要对老大说﹐老大﹐你要的程序已经写好了﹐浏览和管理的页面分别是...

你爱怎么用就怎么用吧﹐什么? 权限? 管我什么事?

 

呵呵﹕)

 

就到这里﹐欢迎讨论。

 

posted on 2008-05-29 15:50 Kevin Zou 阅读(3796) 评论(28)  编辑 收藏 网摘 所属分类: asp.net

Feedback

http://www.cnblogs.com/jyk/archive/2008/05/01/1178753.html" target="_new">http://www.cnblogs.com/jyk/archive/2008/05/01/1178753.html
可以参考这里,我的一个通用的权限的思路。
这里的角色,可以让客户自己设置。

也是主要解决的是你的第一种情况,我觉得我的好像可以叫做零耦合了。

  回复  引用  查看    

要是浏览者手动的把

post.aspx?type=show
改成了
post.aspx?type=manage

会怎么样呢?

  回复  引用  查看    

#3楼 2008-05-29 16:30 李战      
http://www.cnblogs.com/Emoticons/qface/055243188.gif" alt="" />好,受教了。

能不能说说“另一类称为数据权限”的东东,该如何抽象?http://www.cnblogs.com/Emoticons/qface/055243523.gif" alt="" />

  回复  引用  查看    

#4楼 2008-05-29 16:31 kevin003[未注册用户]
可以参考一下SPRING里面的ACEGI实现的权限管理,通用性比较好.可惜就是太庞大了.
  回复  引用    

#5楼[楼主] 2008-05-29 16:34 小生      
金色海洋(jyk)
是不錯,我有看過:)

我的這篇文章只是一個思路﹐具體如何做并沒有去涉及。
權限這一塊的需求在面對具體應用時還是有很多"客制化"的(如角色﹐群組﹐特權﹐再授權等)﹐具體做系統時﹐有用配置文件﹐數據表﹐有的直接做一個權限類﹐將功能全寫在類里面。。。

  回复  引用  查看    

#6楼[楼主] 2008-05-29 16:37 小生      
--引用--------------------------------------------------
金色海洋(jyk): 要是浏览者手动的把

post.aspx?type=show
改成了
post.aspx?type=manage

会怎么样呢?
--------------------------------------------------------
這一塊就需要那個權限模塊來做了。
在檢查url時﹐我們會連同需要檢查的QueryString一起組成url去檢查是否有權限的﹐這和檢查a.aspx和b.aspx的區別是一樣的﹐當然﹐要復雜一些...

  回复  引用  查看    

#7楼[楼主] 2008-05-29 16:39 小生      
--引用--------------------------------------------------
kevin003: 可以参考一下SPRING里面的ACEGI实现的权限管理,通用性比较好.可惜就是太庞大了.
--------------------------------------------------------
我對于"通用"已經很不感冒了﹐
曾經做了很多"通用"東西﹐但是最后在面對一個真正的應用時﹐還是需要改呀改﹐最終越來越大﹐難以維護

現在的思想就是輕﹐靈活﹐重用

一句話﹕簡單就是美~

  回复  引用  查看    

#8楼[楼主] 2008-05-29 16:46 小生      
其實重用的一個很重要的原則就是不要去做通用的東西﹐特別是"要我怎么樣都行"﹐因為說實話﹐很難﹐我們每個人都可能局限在自己的一片天地中﹐不可能將所有情況都考慮到。
結合一種特定的應用﹐作出適合于這種情況的一種解決方案﹐以后再又碰到這種情形時﹐可以盡量的重用就行。

比如說﹐某某﹐你那里有沒有權限管控的東東呀﹐我這邊需要管控這几種角色訪問這几個頁面。
然后你一看﹐哦﹐行﹐我這里有一套方法﹐你這樣這樣...

讓它來適應你﹐而不是你去適應它


  回复  引用  查看    

还是没有理解

如果说 post.aspx?type=manage 这种形式就是有删除权限,那么我只有浏览权限,但是我在IE里面直接输入 post.aspx?type=manage ,我是不是就拥有了删除权限呢?

如果说要到权限类里面去找我到底有没有 manage 权限的话,那么

if(request["type"]=="manage")

删除按钮.visible =

不就是重复了吗?


==================

重用和通用的区别是甚么呢?我觉得所有的功能细节都能够重用的话,那么不就是通用了吗?

  回复  引用  查看    

比如說﹐某某﹐你那里有沒有權限管控的東東呀﹐我這邊需要管控這几種角色訪問這几個頁面。
然后你一看﹐哦﹐行﹐我這里有一套方法﹐你這樣這樣...

讓它來適應你﹐而不是你去適應它

=============

你说的这个不就是通用吗?

  回复  引用  查看    

#11楼[楼主] 2008-05-29 16:51 小生      
--引用--------------------------------------------------
金色海洋(jyk): 还是没有理解

如果说 post.aspx?type=manage 这种形式就是有删除权限,那么我只有浏览权限,但是我在IE里面直接输入 post.aspx?type=manage ,我是不是就拥有了删除权限呢?

如果说要到权限类里面去找我到底有没有 manage 权限的话,那么

if(request["type"]=="manage")

删除按钮.visible =

不就是重复了吗?


==================

重用和通用的区别是甚么呢?我觉得所有的功能细节都能够重用的话,那么不就是通用了吗?
--------------------------------------------------------
其實這已脫離了這篇文章的范疇
web上所有的url都是公開的。
假設你管理貼子的程式是:manager.aspx
那普通用戶也可以直接在瀏覽器中直接輸入manager.aspx呀
這個時候你是怎么解決的呢?

如果你OK了﹐那你問的問題也是如此呀。歡迎再討論~

  回复  引用  查看    

#12楼[楼主] 2008-05-29 16:54 小生      
--引用--------------------------------------------------
金色海洋(jyk): 比如說﹐某某﹐你那里有沒有權限管控的東東呀﹐我這邊需要管控這几種角色訪問這几個頁面。
然后你一看﹐哦﹐行﹐我這里有一套方法﹐你這樣這樣...

讓它來適應你﹐而不是你去適應它

=============

你说的这个不就是通用吗?
--------------------------------------------------------
是呀﹐是在一定范圍內的通用﹐前提是它的需求剛好在你的解決方案之內。
如果它提出的要求你的方案不滿足﹐你可能就不會給它推荐這個方案。
但是你可能會修改你的方案去增加它的應用范圍。
這就涉及到你當初設計這個方案的規划了﹕
高度抽象﹐功能單一﹐依賴倒置等。
有空好好和你聊聊﹐感覺我們的應用范圍很一致...

  回复  引用  查看    

#13楼[楼主] 2008-05-29 16:57 小生      
很多時候我們真的很難考慮到所有情形﹐只有很多經歷之后也許才可以做出一些很"通用"的方案。

但在這之前我們是不是就不可以做設計呢?
是不是就不可以做出重用的東西呢。
。。。

有空再專門寫一篇設計隨筆...

  回复  引用  查看    

问题是,你是这么写的呀

if(request["type"]=="manage")

要是一个页面有四种“权限”的话,(添加、修改、浏览、打印),那的多出来多少的“记录”呀。


我的理解:在一定的范围内通用,也是通用,只是范围小了一点。

.net是一个很大范围的通用。

DataGrid,也是一个比较大的范围的通用,

如果我自己写一个显示数据用的控件的话,那么通用的范围(或者叫做适用的范围)就会小得多,但是我觉得还是可以叫做通用的。在一定的范围内通用:)


  回复  引用  查看    

动态授权,如果按你这种方式去做,问题会很大.
  回复  引用    

这个不好吧?这个按钮要是有好几个,一些人能看见2,一些能看见3,还有些能看见10个,分来分去你是不是要搞出n多的页面来?你这个耦合降下来之后,代码的复用怎么实现?总不能n多页面就写n套代码,那些相同的代码也一并拷贝n份吧?降低耦合是很好的事情,但不等于一味的不耦合。而就按你的方案,权限最终还是得判断的,如何判断呢?到最后你还得写 user.isEditable, user.isDeletable等等,你是不是还得hardcode?其实权限问题现在用户组或角色的解决方案已经很成熟了,对于阅读帖子的模块,可以做一个权限管理功能,让管理员可以设置哪些用户或组可以执行哪些操作,然后在加载页面时,按这个权限矩阵来判定用户到底有哪些权限以显示相应的按钮或菜单就行了,也不用非得hardcode
  回复  引用    

#17楼 2008-05-29 17:37 怪怪      
好久不见 :)
  回复  引用  查看    

#18楼 2008-05-29 17:52 lzppcc[未注册用户]
和我们开发的权限想法差不多.
http://framework.supesoft.com

  回复  引用    

#19楼 2008-05-29 17:54 炭炭      
正确的方式应该是将验证方法放到一个单独的类中吧,如果不想依赖具体类,就用接口和工厂。
另外就像有人说的,用URL参数判断角色信息不好吧,要用session 或 cookie 或Windows帐号阿。

感觉应该形如

IAuthetic myAuthetic=classFactory.getAutheticClass();
If (myAuthetic.getRole(session["UserID"]).hasManageRight())
button.visible=true;
else
button.visible=false;

这样你也可以大声和老大说“我出来买酱油的,权限管我屁事”

  回复  引用  查看    

#20楼 2008-05-29 17:57 huankfe[未注册用户]
看过之后感觉很清爽,在discuz!NT里面就有
  回复  引用    

#21楼 2008-05-29 18:09 水言木      
@小生
我也没有理解你对"金色海洋"的这个回答
-----------------------------------
假設你管理貼子的程式是:manager.aspx
那普通用戶也可以直接在瀏覽器中直接輸入manager.aspx呀
這個時候你是怎么解決的呢?
-----------------------------------
你说的manager.aspx这种情况是普通用户完全没有权限看, 这只要在页面显示前阻拦即可,
但LZ文中的情况是这个"用户"同样可以看, 只是说他跟管理员看到的有几个按扭不同而已, 这两种情况并不一样啊

  回复  引用  查看    

#22楼 2008-05-29 19:22 tsoukw[未注册用户]
@金色海洋(jyk)

--引用--------------------------------------------------
金色海洋(jyk): 问题是,你是这么写的呀

if(request["type"]=="manage")

要是一个页面有四种“权限”的话,(添加、修改、浏览、打印),那的多出来多少的“记录”呀。

--------------------------
实际情形是不会有这么多权限的,一般user的角度都是角色来划分的,所以实际在开发系统时,粗粒度大部分时候是可以应付的。
其实,就算要分这么多也是很好做的,使用request["Type"]属性的值完全依当支程式自己来理解,如01011这样的方式就是很不错的组合


我的理解:在一定的范围内通用,也是通用,只是范围小了一点。

.net是一个很大范围的通用。

DataGrid,也是一个比较大的范围的通用,

如果我自己写一个显示数据用的控件的话,那么通用的范围(或者叫做适用的范
围)就会小得多,但是我觉得还是可以叫做通用的。在一定的范围内通用:)


--------------------------------------------------------
你说得对,我可能表达不清楚,其实实现大范围的通用肯定是目标,只是过程可以循序渐进,且一定把握住单一职责和低耦合的方式添加新应用,其实我看了你的权限方案,是能够应用到很多场合的。但是你有没有想过,有人可能觉得你的方案非常不错时,例如控件按权限显示方式,但是他不想建你的数据表,因为它就一个系统,想用一个配置文件来实现,这时候你的解决方案能不能很好的满足它呢?
你是否可以做一些更小粒度的小模块让使用者来选择使用呢?

一点点小看法,可能你也有考虑:)

  回复  引用    

#23楼 2008-05-29 19:24 tsoukw[未注册用户]
--引用--------------------------------------------------
怪怪: 好久不见 :)
--------------------------------------------------------
前段时间可能很忙,没空写

但是会每天上来看,尤其是你的随笔,真的不错!

  回复  引用    

#24楼 2008-05-29 19:27 tsoukw[未注册用户]
--引用--------------------------------------------------
水言木: @小生
我也没有理解你对"金色海洋"的这个回答
-----------------------------------
假設你管理貼子的程式是:manager.aspx
那普通用戶也可以直接在瀏覽器中直接輸入manager.aspx呀
這個時候你是怎么解決的呢?
-----------------------------------
你说的manager.aspx这种情况是普通用户完全没有权限看, 这只要在页面显示前阻拦即可,
但LZ文中的情况是这个"用户"同样可以看, 只是说他跟管理员看到的有几个按扭不同而已, 这两种情况并不一样啊
--------------------------------------------------------
一样呀,其实web上的东西都是开放的,你是如何在页面之前阻拦manager.aspx的呢,你也一样可以呀?
如它访问post.aspx?type=manage时,
你可以用
if (request.url == "post.aspx" && request["type"]=="manager" && 没有管理权限时)
转向出错页面...

  回复  引用    

我是把适用范围定得很小的,我不想让他的范围很广,因为部又不能把它当作产品来卖,为什么要项产品那样的去适应更广泛的应用范围呢?

如果我要做产品的话,我会考虑的,但是现在只是做好我负责的项目。

产品:类似于中间件的东东。

  回复  引用  查看    

#26楼 2008-05-30 11:14 kkun      
另一类我将它称为数据权限﹐如某某人可以查看某某部门的人员信息

这一类才是关键~

  回复  引用  查看    

#27楼 2008-05-30 15:09 阿七[未注册用户]
看完还是有点收获

权限判断应该用统一的一个方法(在一个类中)判断,当然方法应该带一个参数,如权限路径

用户的身份应当保存在Session或Cookies中(你说的好像是Web开发吧),地址栏怎么可以标识用户身份????

用户没有的权限,当然不提供对应的菜单和链接,你想用户点击后才知道能不能进行操作????

  回复  引用    

#28楼 2008-05-30 17:02 傻样精英      

  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1210050




相关文章:

相关链接: