前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >权限之数据权限概念原型实现抽象

权限之数据权限概念原型实现抽象

作者头像
幺鹿
发布2018-08-21 16:04:22
9430
发布2018-08-21 16:04:22
举报
文章被收录于专栏:Java呓语Java呓语

概念

无论为数据操作赋予怎样的业务含义,其本质上仍然是数据的增删改查操作(如下图)。

image.png

随着业务的演进,逐渐衍生出精细化管理数据的诉求。我遇到的业务场景是在企业级数据管理中,对不同职级的员工展示不同的数据。我的业务上的诉求是对SELECT进行权限控制,对INSERTUPDATEDELETE没有权限限制要求。

数据权限实现的复杂度还是较高的,在叙述实现之前,我们先预设期望的结果: 能够将繁琐的细节都封装在内部逻辑中,对外部提供统一的接口调用。

相关设计理念可以参考我之前写的文章,《外观模式(封装交互,简化调用)》

在这个模型中,我们可选切入点有:

  • 用户层面进行业务逻辑判断(不推荐)
  • SQL层面上的抽象
  • 数据库视图(不推荐)

我在这里选择了使用SQL来完成数据权限的实现,通过SQL的组装来完成宽泛的数据权限的控制。

原型实现

背景:某超市拥有员工 5 名,其组织架构如下图。该小超市的信息化程度极高,已经拥有完备的移动版的CRM系统。

image.png

诉求:

  • 店长可以看到所有的销售数据;
  • 营业员可以看到自己的销售数据,但是不能看到别人的销售数据;
  • 收银出纳可以看到所有人的销售数据;
  • 采购库管不能看到销售数据;

先贴上原型实现,说明流程:

代码语言:javascript
复制
// 规则对象用于定义规则
// 这些规则包括用户定义和管理员定义
// 规则应可以序列化(此处省略)
public class RuleDataVO {

    // 受众群体
    private String audienceGroup;
    // 规则实体
    private String rule;

    public RuleDataVO(String audienceGroup, String rule) {
        this.audienceGroup = audienceGroup;
        this.rule = rule;
    }

    public String getAudienceGroup() {
        return audienceGroup;
    }

    public void setAudienceGroup(String audienceGroup) {
        this.audienceGroup = audienceGroup;
    }

    public String getRule() {
        return rule;
    }

    public void setRule(String rule) {
        this.rule = rule;
    }
}
代码语言:javascript
复制
public class SaleDataVO {
    private Long userId;
    private Long storeId;
    private String goodName;
    private Integer goodPrice;

    public SaleDataVO(Long userId, Long storeId, String goodName, Integer goodPrice) {
        this.userId = userId;
        this.storeId = storeId;
        this.goodName = goodName;
        this.goodPrice = goodPrice;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getStoreId() {
        return storeId;
    }

    public void setStoreId(Long storeId) {
        this.storeId = storeId;
    }

    public String getGoodName() {
        return goodName;
    }

    public void setGoodName(String goodName) {
        this.goodName = goodName;
    }

    public Integer getGoodPrice() {
        return goodPrice;
    }

    public void setGoodPrice(Integer goodPrice) {
        this.goodPrice = goodPrice;
    }

    @Override
    public String toString() {
        return goodName + ":" + goodPrice / 100.0;
    }
}
代码语言:javascript
复制
// 用于模拟数据库操作
public class MockDataSource {

    // 模拟销售数据库表
    public static class SaleDataTable {
        private static List<SaleDataVO> list = new ArrayList<>();

        static {
            list.add(new SaleDataVO(1L, 1L, "贝因美奶粉", 9800));
            list.add(new SaleDataVO(1L, 1L, "毛巾", 2810));
            list.add(new SaleDataVO(2L, 1L, "黑人牙膏", 3200));
        }

        public static List<SaleDataVO> retrieval(Long userId, RuleDataVO ruleDataVO) {


            String ruleContent = ruleDataVO.getRule();
            if (ruleContent != null) {
                if (Objects.equals("自身", ruleContent)) {
                    // 如果检查是营业员
                    if (userId == 1 || userId == 2) {
                        return filter(userId);
                    } else {
                        return filter(null);
                    }
                } else if (Objects.equals("本门店内", ruleContent)) {
                    return filter(1L, 2L);
                } else if (Objects.equals("无", ruleContent)) {
                    return filter(null);
                }
            }
            return filter(null);
        }

        private static List<SaleDataVO> filter(Long... userIds) {
            List<SaleDataVO> saleDataVOS = new ArrayList<>();
            if (userIds == null) return saleDataVOS;
            for (SaleDataVO saleDataVO : list) {
                for (Long userId : userIds) {
                    if (saleDataVO.getUserId().equals(userId)) {
                        saleDataVOS.add(saleDataVO);
                        break;
                    }
                }
            }
            return saleDataVOS;
        }
    }

    // 模拟规则库表
    public static class RuleTable {
        private static List<RuleDataVO> list = new ArrayList<>();

        static {

            list.add(new RuleDataVO("营业员", "自身"));
            list.add(new RuleDataVO("店长", "本门店内"));
            list.add(new RuleDataVO("收银出纳", "本门店内"));
            list.add(new RuleDataVO("采购库管", "无"));
        }

        public static void add(RuleDataVO dataVO) {
            list.add(dataVO);
        }

        public static RuleDataVO retrieval(String audienceGroup) {
            for (RuleDataVO dataVO : list) {
                if (dataVO.getAudienceGroup().equalsIgnoreCase(audienceGroup)) {
                    return dataVO;
                }
            }
            return null;
        }
    }


}
代码语言:javascript
复制
public class Main {

    public static void main(String[] args) {
        System.out.println(MockDataSource.SaleDataTable.retrieval(1L, MockDataSource.RuleTable.retrieval("营业员")));
        System.out.println(MockDataSource.SaleDataTable.retrieval(2L, MockDataSource.RuleTable.retrieval("营业员")));
        System.out.println(MockDataSource.SaleDataTable.retrieval(3L, MockDataSource.RuleTable.retrieval("店长")));
        System.out.println(MockDataSource.SaleDataTable.retrieval(4L, MockDataSource.RuleTable.retrieval("收银出纳")));
        System.out.println(MockDataSource.SaleDataTable.retrieval(5L, MockDataSource.RuleTable.retrieval("采购库管")));
    }
}

// 调用结果:
[贝因美奶粉:98.0, 毛巾:28.1]
[黑人牙膏:32.0]
[贝因美奶粉:98.0, 毛巾:28.1, 黑人牙膏:32.0]
[贝因美奶粉:98.0, 毛巾:28.1, 黑人牙膏:32.0]
[]

在这个原型上省略了不必要的复杂性(如DB操作,业务操作),仅关注规则的定义与解析过程。

原型上简单定义了自身 本门店内 的语法规则,结合上下文判拼接处正确的SQL语句。

我理解的权限控制核心就在这里:定义语法规则解析并应用到SQL规范中。

  • 后端上定义语法规则,预初始化入库,即完成数据权限的控制。
  • 前端上定义语法规则(需考虑SQL注入问题),即时操作入库,即完成数据权限的控制;

上述是个非常简单的原型,说明了解题思路但是实际的可操作性不高。因此我们需要接着对它进行抽象。

抽象

指令:查询当天的销售数据;

环境:基于上下文参数推断出所属的资源,如:所属的公司、部门等;

权限:仅自身相关的数据、本部门内、本部门及下属部门、所有、无;

对象

环境

权限

SQL

营业员

好又多超市101分店

仅自身相关的数据

uid=$uid

收银出纳

好又多超市101分店

本部门及下属部门

uid in $uids

采购库管

好又多超市101分店

uid = null

店长

好又多超市101分店

本部门及下属部门

uid in $uids

实现步骤拆分

  • 组织树
  • 人与组织树
  • 角色与功能权限
  • 角色与数据权限
  • 角色与人
  • 应用权限规则

组织树

image.png

通常业务限定组织树的深度都不会过高,一般在5层以内。实现组织树的方式有多种:

定义组织树目的是承载人的容器,通过将人分配到对应组织中完成上下文的联系。

image.png

通过将人分配到不同的部门中,即完成了人与组织的关系。这样我们就能通过上下文推导出人所具备的资源。

通过对组织的资源限制即可完成预初始化状态的SQL配置,如:

代码语言:javascript
复制
// 大老板
SELECT * FROM table;
// 李妲
 SELECT * FROM table WHERE department in ('行政部','销售部');
// 李达
 SELECT * FROM table WHERE department = '行政部';
// 肖雨
 SELECT * FROM table WHERE store = '六盘水...;

解题步骤

代码语言:javascript
复制
* 添加组织(建设组织树)
* 添加人
* 添加功能权限
* 分配角色到功能权限
* 添加数据权限
* 分配角色到数据权限
* 分配人到组织
* 分配人到角色

目前整个数据权限管理流程已经做通了,具体的代码涉及业务细节就不贴出来了。

如果有各种疑问,欢迎提问。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 原型实现
  • 抽象
    • 组织树
      • 解题步骤
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档