前一篇文章《通用权限管理设计 之 数据库设计方案》介绍了【主体】- 【领域】 - 【权限】( who、what、how问题原型 ) 的设计思想html
本文将对这种设计思想做进一步的扩展,介绍数据权限的设计方案。前端
权限控制能够理解,分为这几种 :web
【功能权限】:能作什么的问题,如增长产品。
【数据权限】:能看到哪些数据的问题,如查看本人的全部订单。
【字段权限】:能看到哪些信息的问题,如供应商帐户,看不到角色、 部门等信息。sql
上面提到的那种设计就是【功能权限】,这种设计有必定的局限性,对于主体,只能明确地指定。对于不明确的,在这里可能就没办法处理。好比下面这几种状况:数据库
数据仅当前部门及上级可见
数据仅当前用户(本人)可见安全
相似这样的就须要用到上面提的数据权限。服务器
上一篇文章我用一个表五个字段完成了【功能权限】的设计思路
本文我将介绍如何利用一个表两个字段完成这个【数据权限】的设计思路数据结构
【数据权限】是在【功能权限】的基础上面进一步的扩展,好比能够查看订单属于【功能权限】的范围,可是能够查看哪些订单就是【数据权限】的工做了。less
在设计中,咱们规定好若是没有设置了数据权限规则,那么视为容许查看所有的数据。数据库设计
几个概念
【资源】:数据权限的控制对象,业务系统中的各类资源。好比订单单据、销售单等。属于上面提到的【领域】中的一种
【主体】:用户、部门、角色等。
【条件规则】:用于检索数据的条件定义
【数据规则】:用于【数据权限】的条件规则
应用场景
1,订单,能够由本人查看
2,销售单,能够由本人或上级领导查看
3,销售单,销售人员能够查看本身的,销售经理只查看 销售金额大于100,000的。
咱们能想到直接的方法,在访问数据的入口加入SQL Where条件来实现,组织sql语句:
1,where UserID = {CurrentUserID}
2,where UserID = {CurrentUserID} or {CurrentUserID} in (领导)
3,where UserID = {CurrentUserID} or ({CurrentUserID} in (销售经理) and 销售金额 > 100000)
这些一个一个的"条件",本文简单理解为一个【数据规则】,一般会与原来咱们前台的业务过滤条件合并再检索出数据。
这是一种最直接的实现方式,在【资源】上面加一个【数据规则】(好比上面的三点)。这样设计就是
【资源】 - 【数据规则】
我又以为不一样的人应该对应不一样的规则,那么也能够理解为,一个用户对应不一样的角色,每个角色有不同的【数据规则】,那么设计就变成
【资源】 - 【主体】 - 【数据规则】
根据提供者的不一样,准备不一样的权限应对策略。
这里能够简单地介绍一下,这个方案至少须要2张表,一个是 【资源,主体,规则关系表】、一个是【数据规则表】
关系表不能直接保存角色,由于你不肯定何时业务须要按照【部门】或者【分公司】来定义数据规则
因而能够用 Master、MasterKey 相似这样的两个字段来肯定一个【主体】
用XML方式的话是这样配置的(放在数据库也相似):
<?xml version="1.0" encoding="utf-8"?>
<settings>
<rule view="订单" role="销售人员">
销售员 = {CurrentUserID}
</rule>
<rule view="订单" role="总销售经理">
销售金额 > 100000
</rule>
<rule view="订单" role="区域销售经理">
销售金额 > 100000 and 区域 = {当前用户所属区域}
</rule>
</settings>
对于这种方式有兴趣的朋友也能够试一下,两种方式的【数据规则】是同样的,可是本文没有采用第二种设计方式,由于它多了一层处理逻辑,我觉得应该设计越简单越好,就采用第一种方式:
【资源】 - 【数据规则】
固然,上面是用SQL的方式来肯定条件规则的,咱们固然不会这么作。SQL虽然灵活,可是咱们很难去维护,也不知道SQL在咱们的界面UI上面如何体现。难不成直接用一个文本框来显示。这样对应一个开发人员来讲不是问题,但是对应系统管理员,很容易出问题。因此咱们须要有另外一方式来肯定这一规则,并最终能够转换成咱们的SQL语句。
咱们的设计关键在于如何规范好这些【数据规则】 ,这个规则必须是对前端友好的,并且是对后台友好的,JSON显然是很好的方式。
规则说明:
1,数据权限规则老是:{属性 条件 容许值}
2,数据权限规则能够合并。好比 ( {当前用户 属于 销售人员} and {订单销售员 等于 当前用户} ) Or {当前用户 属于 销售经理}
3,最终咱们会用JSON格式
在检索数据时会先判断有没有注册了某某【资源】的【条件规则】,若是有,那么加载这个【条件规则】并合并到咱们前台的【搜索条件】(你的业务界面应该有一个搜索框吧)
以下图定义了客户信息的搜索框,咱们搜索客户ID包括AN,咱们组织成的规则将会是:
{"rules":[{"field":"CustomerID","op":"like","value":"AN","type":"string"}],"op":"and"}
为了更好地理解【数据规则】,这里介绍一下【通用查询机制】
权限控制总离不开一些条件的限制(好比查看当前部门的单据),若是没有完善的通用查询规则机制,那么在作权限条件过滤的时候你会以为很别扭。这里介绍一个通用查询方案,而后再介绍如何实现【数据规则】。
早些时候我写过一篇关于ligerGrid结合.net设计通用处理类的文章《 jQuery liger ui ligerGrid 打造通用的分页排序查询表格(提供下载) 》。里面提到的过滤信息是直接的SQL语句。这是不可靠,并且不安全的。
在前端传输给后台的过滤信息不该该是直接的SQL,而应该是一组过滤规则。在ligerui V1.1.8 已经加入了一个条件过滤器插件,这个插件组成的规则数据才是我受推荐的:
好比以下
{"rules":
[
{"field":"OrderDate","op":"less","value":"2012-01-01"},
{"field":"CustomerID","op":"equal","value":"VINET"}
]
,"op":"and"}
规则描述:
查找顾客VINET全部订单时间小于2011-01-01的单据
这样的数据是安全的,并且是通用的(你甚至能够再加一个OR子查询)。不管是在前端仍是后台,不管你使用什么样的组件,均可以很好地利用。
通用后台的翻译,就能够生成这样SQL的参数:
Text:
([OrderDate] < @p1 and [CustomerID] = @p2)
Parameters:
p1:2012-01-01
p2:VINET
下面来点复杂的:查找 顾客VINET或者TOMSP,全部订单时间小于2011-01-01的单据
{
"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"}, {"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],
"op":"and"
}
翻译结果:
Text:([OrderDate] < @p1 and ([CustomerID] = @p2 or [CustomerID] = @p3))
Parameters:
p1:2012-01-01
p2:VINET
p3:TOMSP
这个过滤规则分为三个部分:【分组】、【规则】(字段、值、操做符)、【操做符】(and or),而自身就是一个分组。
这种简单的结构就能够知足所有的状况。
固然,上面提到的这些条件都是在前台定义(多是用户在搜索框本身输入的)的,而在后台,咱们可能会定义一下【隐藏条件】,好比说 【员工只能查看本身的】,要怎么作呢,其实很简单,只须要在后台接收到这个过滤条件(前台toJSON,后台解析JSON)之后,再加上一个过滤规则(隐藏条件):
{field:'EmployeeID',op:'equal',value:5}
能够将原来的过滤规则当作一个分组加入进行:
{op:'and',groups:[
{"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"},{"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],"op":"and"}
],rules:[{field:'EmployeeID',op:'equal',value:5}]
}
翻译以下:
Text:
([EmployeeID] = @p1 and ([OrderDate] < @p2 and ([CustomerID] = @p3 or [CustomerID] = @p4)))
Parameters:
p1:5
p2:2012-01-01
p3:VINET
p4:TOMSP
这样的【条件规则】才是咱们想要的,不只在前端能够很好地解析,也能够在后台进行处理。在后台咱们会定义跟这种数据结构对应的类,那么再定义一个翻译成SQL的类:
说了这些,能够开始介绍如何实现【数据规则】了:
上面提到的【隐藏条件】,就是我介绍的【数据规则】
试想一些,这样 前台的过滤规则,再加上咱们之间定义好的 【数据权限】控制 过滤条件。不就达到目的了吗。
先看看咱们在数据库里保存的这些【数据规则】:
看不明白?那来个清楚一点的:
规则描述
订单:【订单管理员和演示角色能够查看全部的】,【订单查看员】只能查看本身的
产品:【基础信息录入员和演示角色能够查看全部的】,【供应商】只能查看本身的
{CurrentEmployeeID}表示当前的员工。
实质上,咱们还能够根据当前用户信息定义须要的参数,好比:
{CurrentUserID} 当前用户Id ,对应表【CF_User】
{CurrentRoleID} 当前角色Id ,对应表 【CF_Role】
{CurrentDeptID} 当前用户部门Id,对应表【CF_Department】
{CurrentEmployeeID} 当前用户员工Id,对应表【Employees】(CF_User.EmployeeID)
{CurrentSupplierID} 当前用户供应商Id,对应表【Suppliers】(CF_User.SupplierID)
在数据库中咱们直接保存这些用户参数,在“翻译”规则成为SQL时,会替换掉:
好比查看订单,咱们获得的SQL,多是这样的:
Text: SELECT * FROM (SELECT TOP 20 * FROM (SELECT TOP 40 * FROM [Orders] WHERE ( 1=1 and ((@p1 in (@p2,@p3)) or (@p4 = @p5 and [EmployeeID] = @p6))) ORDER BY OrderID ASC) AS tmptableinner ORDER BY OrderID DESC) AS tmptableouter ORDER BY OrderID ASC
Parameters:
@p1[Int32] = 7
@p2[Int32] = 2
@p3[Int32] = 6
@p4[Int32] = 7
@p5[Int32] = 7
@p6[Int32] = 1
{CurrentRuleID} 替换为 7
{CurrentEmployeeID} 替换为1
下图是咱们设计【数据权限】的界面,能够选择全部的字段,包括几个用户信息:
这些字段不只仅只是在文本框中输入值,那么能够自定义数据来源:
var fieldEditors = {};
fieldEditors['Orders'] = {
'ShipCity': { type: 'combobox',
options: {
width: 200,
url: "../handler/select.ashx?view=Orders&idfield=ShipCity&textfield=ShipCity&distinct=true"
}
}
};
效果界面:
既然是数据权限控制,若是有一个统一的数据接收入口,咱们却是能够利用这个入口作一些工做。
好比【ligerRM权限管理系统】统一使用 grid.ashx 这个数据处理程序做为列表数据的接收入口。
有了统一的接口,方便作权限的控制,使用过 ligerGrid Javascript表格,或者相似插件的朋友,应该比较清楚服务器的交互原理。
在grid.ashx中,咱们会经过
【视图/表名 】、 【排序信息】、【分页信息】、【过滤信息】
这几个指标来获取指定的数据。
而在实际的业务中,可能会引入权限的控制。好比某某【资源】,只能由当前用户自身才能查看,或者只能由当前用户部门及上级部门才能查看。对于这些控制,咱们采用对这些可能作权限控制的【资源】注册一组【条件规则】的方式来进行。
咱们将找到view定义好的【数据权限规则】,而后和用户在前台搜索框输入的【搜索规则】合并:
上面的代码就是数据条件合并的例子,这样便获得了咱们最终须要的 过滤规则。
本文提出了数据权限的一种实现思路,只是本人在作一个web应用时构思的方案,谈不上规范,欢迎提出你的建议意见。
能够在http://case.ligerui.com体验实际的应用效果。