设计案例之通用权限管理设计数据权限

本文将对这种设计思想作进一步的扩展,介绍数据权限的设计方案。

权限控制可以理解,分为这几种 :

【功能权限】:能做什么的问题,如增加产品。

【数据权限】:能看到哪些数据的问题,如查看本人的所有订单。

【字段权限】:能看到哪些信息的问题,如供应商账户,看不到角色、 部门等信息。

   上面提到的那种设计就是【功能权限】,这种设计有一定的局限性,对于主体,只能明确地指定。对于不明确的,在这里可能就没办法处理。比如下面这几种情况:

数据仅当前部门及上级可见

数据仅当前用户(本人)可见

类似这样的就需要用到上面提的数据权限。

初步分析

【数据权限】是在【功能权限】的基础上面进一步的扩展,比如可以查看订单属于【功能权限】的范围,但是可以查看哪些订单就是【数据权限】的工作了。

在设计中,我们规定好如果没有设置了数据权限规则,那么视为允许查看全部的数据。

几个概念

【资源】:数据权限的控制对象,业务系统中的各种资源。比如订单单据、销售单等。属于提到的【领域】中的一种

主体】:用户、部门、角色等。

【条件规则】:用于检索数据的条件定义

【数据规则】:用于【数据权限】的条件规则

应用场景

**1,订单,可以由本人查看 **

**2,销售单,可以由本人或上级领导查看 **

3,销售单,销售人员可以查看自己的,销售经理只查看 销售金额大于100,000的。

我们能想到直接的方法,在访问数据的入口加入SQL Where条件来实现,组织sql语句:

1,whereUserID={CurrentUserID}

2,whereUserID={CurrentUserID}or{CurrentUserID}in(领导)

3,whereUserID={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"}

image

为了更好地理解【数据规则】,这里介绍一下【通用查询机制】

【通用查询机制】

   权限控制总离不开一些条件的限制(比如查看当前部门的单据),如果没有完善的通用查询规则机制,那么在做权限条件过滤的时候你会觉得很别扭。这里介绍一个通用查询方案,然后再介绍如何实现【数据规则】。

早些时候我写过一篇关于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]<@p1and([CustomerID]=@p2or[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的类:
image

数据权限规则

说了这些,可以开始介绍如何实现【数据规则】了:

上面提到的【隐藏条件】,就是我介绍的【数据规则】

试想一些,这样 前台的过滤规则,再加上我们之间定义好的 【数据权限】控制 过滤条件。不就达到目的了吗。

先看看我们在数据库里保存的这些【数据规则】:

image

看不明白?那来个清楚一点的:

image

规则描述

订单:【订单管理员和演示角色可以查看所有的】,【订单查看员】只能查看自己的

产品:【基础信息录入员和演示角色可以查看所有的】,【供应商】只能查看自己的

{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时,会替换掉:

image

比如查看订单,我们得到的SQL,可能是这样的:

Text:

SELECTFROM(SELECTTOP20FROM(SELECTTOP40*FROM[Orders]WHERE(1=1and((@p1in(@p2,@p3))or(@p4=@p5and[EmployeeID]=@p6)))ORDERBYOrderIDASC)AStmptableinnerORDERBYOrderIDDESC)AStmptableouterORDERBYOrderIDASC

Parameters:

@p1[Int32]=7

@p2[Int32]=2

@p3[Int32]=6

@p4[Int32]=7

@p5[Int32]=7

@p6[Int32]=1

{CurrentRuleID} 替换为 7

{CurrentEmployeeID} 替换为1

下图是我们设计【数据权限】的界面,可以选择所有的字段,包括几个用户信息:

image

这些字段不仅仅只是在文本框中输入值,那么可以自定义数据来源:

varfieldEditors={};

fieldEditors['Orders']={

'ShipCity': { type:'combobox',

options: {

width:200,

url: "../handler/select.ashx?view=Orders&idfield=ShipCity&textfield=ShipCity&distinct=true"

}

}

};

效果界面:

image

实际应用

既然是数据权限控制,如果有一个统一的数据接收入口,我们倒是可以利用这个入口做一些工作。

比如【ligerRM权限管理系统】统一使用 grid.ashx 这个数据处理程序作为列表数据的接收入口。

有了统一的接口,方便做权限的控制,使用过 ligerGrid Javascript表格,或者类似插件的朋友,应该比较清楚服务器的交互原理。

在grid.ashx中,我们会通过

【视图/表名 】、 【排序信息】、【分页信息】、【过滤信息】

这几个指标来获取指定的数据。

而在实际的业务中,可能会引入权限的控制。比如某某【资源】,只能由当前用户自身才能查看,或者只能由当前用户部门及上级部门才能查看。对于这些控制,我们采用对这些可能做权限控制的【资源】注册一组【条件规则】的方式来进行。

image

我们将找到view定义好的【数据权限规则】,然后和用户在前台搜索框输入的【搜索规则】合并:

上面的代码就是数据条件合并的例子,这样便得到了我们最终需要的 过滤规则。

结语

本文提出了数据权限的一种实现思路,只是本人在做一个web应用时构思的方案,谈不上规范,欢迎提出你的建议意见。

可以在http://case.ligerui.com体验实际的应用效果。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容