浅谈数据权限的实现方法和作用机制

在企业开发中,往往对权限的控制有较高的要求,其中也经常会被要求实现对业务数据操作进行权限控制。在这里,分享一下本人在企业开发中的数据权限的实现经验。本文所用的方法和实例,可在CSDN的代码托管平台找到。需要的童鞋可点击:https://code.csdn.net/xuanbg/starx-bip自行查看或下载。

要想管理数据权限,首先得定义数据的归属和用户对数据的访问范围,和功能权限相比,这一点是数据权限特有的。那么数据的归属要怎么定义呢?一般来说,可在数据的生产者和数据的生产部门这两个维度进行定义。例如:

序号      收款金额   收款部门   收款人

1           3000.00    A部门        张三

2           2000.00    B部门       李四

3         50000.00   A部门        王五

从上表可以看出来,第一行数据中的3000元是A部门的张三收取的,而第二行数据中的2000元则是B部门的李四收取。如果授权给张三只能看到他自己的业务数据,那么对于张三来说,第二行和第三行数据就不应该看到。如果授权王五能够看到A部门的业务数据,则王五能看到的数据是第一行和第三行。如果授权李四能够管理整个公司的收款业务数据,那么李四应该可以读取上表里的所有数据。

这里就又涉及到了部门、机构这些概念,这里且不深谈。一般来说,业务数据的权限类型是只读和读写两种,数据的访问范围则稍复杂,有如下这么几种:

1、仅本人,意味着用户只能访问其本人产生的业务数据

2、仅本部门,意味着用户可以访问其所在部门所有用户产生的业务数据

3、本部门全部,意味着用户不但可以访问其所在部门所有用户产生的业务数据,还可以访问其所在部门下属部门所有用户产生的业务数据

4、本机构全部,意味着用户可以访问其所在机构(通常是整个分子公司)所有的业务数据

5、整个公司,意味着用户可以获取包括母公司及母公司其它分子公司的全部数据

6、自定义,用户可以以不同的访问权限访问指定部门的业务数据,譬如A部门只读,B部门可读写。

涉及权限,无非是:角色、角色成员和角色权限。功能权限如此,数据权限也没有区别。在数据模型的设计上,就会有如下结构:

角色表:Id,角色名称,以及其它字段

角色成员表:Id,角色Id,成员Id

角色数据权限表:Id,角色Id,数据表名或Id,访问范围访问权限

以上三张表结合起来,我们就可以知道当前登录用户能访问哪些数据表以及只读还是读写权限了。但是仅仅知道这些还是不够的,我们需要一种高效的方法来对数据进行筛选,以便用户能够获取且仅获取他能够获取的数据。然后在每条数据上附加上权限标签来告诉系统这条数据用户是只读还是读写。

在获取数据前,我们首先需要知道特定的数据表(这个由业务模块决定,不同的业务必然使用不同的数据表),当前用户被授权访问什么范围的数据。由于用户可能是多个角色的成员,所以这个需要通过一个表值函数来合并所有角色权限,从而获取当前用户的真正权限。函数源代码如下:

/*****表值函数:获取当前登录用户有效数据访问范围类型和权限*****/


IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'User_DataPerm') AND OBJECTPROPERTY(id, N'ISTABLEFUNCTION') = 1)
DROP FUNCTION User_DataPerm
GO


CREATE FUNCTION User_DataPerm(
@ModuleId              VARCHAR(36),     --模块Id
@UserId                VARCHAR(36),     --当前登录用户Id
@OrgId                 VARCHAR(36)      --当前登录部门Id
)


RETURNS TABLE AS


RETURN
select D.TableId, max(D.Permission) as Permission, D.Mode
  from(
  select R.RoleId
    from Sys_Role_User R
    where R.UserId = @UserId
  union
  select R.RoleId
    from Sys_Role_UserGroup R
    join Sys_UserGroupItem G on G.GroupId = R.GroupId
      and G.UserId = @UserId
  union
  select R.RoleId
    from Sys_Role_Position R
    join Sys_User_Org P on P.OrgId = R.OrgId
      and P.UserId = @UserId
    join Sys_Organization O on O.Id = R.OrgId
      and O.ParentId = @OrgId
  ) R
  join Sys_RolePerm_Data D on D.RoleId = R.RoleId
  join Sys_ModuleTable M on M.Id = D.TableId
    and M.ModuleId = @ModuleId
group by D.TableId, D.Mode
having max(D.Permission) > 0


GO

知道用户被授权的访问范围之后,1和2两种情况简单加个条件过滤就行了,3-6四种情况需要用到表值函数来获取具体的授权访问范围和访问权限。

/*****表值函数:获取当前登录用户数据访问范围和权限*****/


IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'User_Data') AND OBJECTPROPERTY(id, N'ISTABLEFUNCTION') = 1)
DROP FUNCTION User_Data
GO


CREATE FUNCTION User_Data(
@ModuleId              VARCHAR(36),     --模块Id
@UserId                VARCHAR(36),     --当前登录用户Id
@OrgId                 VARCHAR(36)      --当前登录部门Id
)


RETURNS @PermScope  TABLE(
OrgId                  VARCHAR(36),
Permission             INT
) AS
BEGIN
DECLARE @Table         VARCHAR(36)
DECLARE @Code          VARCHAR(36)
DECLARE @Mode          INT
DECLARE @Permission    INT


select @Table = TableId, @Mode = Mode, @Permission = Permission
from dbo.User_DataPerm(@ModuleId, @UserId, @OrgId)


if @Mode = 4
  insert into @PermScope
  select RDI.OrgId, RDI.Permission
  from Sys_RolePerm_DataItem RDI
  join Sys_RolePerm_Data RD on RD.Id = RDI.PermDateId
    and RD.TableId = @Table
else
  begin
  if @Mode = 3
    select @OrgId = dbo.GetOrgId(@OrgId, 0)
  if @Mode = 2
    select @OrgId = dbo.GetOrgId(@OrgId, 1)
  select @Code = Code from Sys_Organization where Id = @OrgId
  insert into @PermScope
  select Id, @Permission
  from Sys_Organization
  where Code like @Code + '%'
  end
RETURN
END
GO


/*****标量值函数:获取上级或根机构Id*****/

IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'GetOrgId') AND OBJECTPROPERTY(id, N'ISSCALARFUNCTION') = 1)
DROP FUNCTION GetOrgId
GO

CREATE FUNCTION GetOrgId (
@DeptId                VARCHAR(36),    --部门Id
@Type                  INT             --机构类型:0、根机构;1、上级机构
)

RETURNS NVARCHAR(36) AS

BEGIN
DECLARE @NodeType     INT = 0
DECLARE @ParentId     VARCHAR(36)

while @NodeType != 1
  begin
  select @NodeType = NodeType * @Type, @ParentId = ParentId from Sys_Organization where Id = @DeptId
  if @ParentId is null
    set @NodeType = 1
  if @NodeType != 1
    set @DeptId = @ParentId
  end
RETURN(@DeptId)
END

GO


在函数里还调用了另一个函数来获取机构或根机构的Id,并以此为依据获取该节点的编码,只要把组织机构的编码设计值为类似01、0101这样的层级编码,就很容易使用一个like来得到所有下属节点的Id。最后,利用inner join 合并两表内容并取交集的特性,把业务数据表和表值函数按组织机构Id 来一次inner join,就能把非授权范围内的数据全部过滤掉,并且把所有业务数据加上访问权限的类型码。


你可能感兴趣的:(架构设计,权限机制)