数据权限的设计与实现系列13——前端筛选器组件Everright-filter集成多控制维度实现

数据权限多维度实现

上面的所有工作,实际都是基于业务实体属性这一数据权限控制维度展开的。
接下来,我们来设计与实现多维度,主要是用户组(即角色)、组织机构和用户。

业务需求分析

用户控制维度

业务场景:销售员只能查看自己的销售订单
实现方式:销售订单的创建人属性=当前用户标识

组织机构控制维度

业务场景:部门内备品备件库存部门内所有成员可见
实现方式:备品备件的归属部门=当前用户所在部门

用户组控制维度

用户组这个数据权限控制维度如果单独使用,本质上与基于RBAC模型的功能权限的控制方式重叠了。因此通常是结合其他控制维度来进行多维度的复杂控制。

业务场景1:销售部门经理可查看本部门所有销售订单
实现方式:
销售订单的创建部门 = 当前用户所在部门
且 当前用户拥有的用户组 包含 销售部门经理


业务场景2:分管销售的副总裁只关注销售额大于100万的销售订单
实现方式:
销售订单的金额 > 100 万
且 当前用户拥有的用户组 包含 分管销售的副总裁

业务场景3:某个人挂靠在A部门下,但是需要处理同级的多个部门如B、C的数据,或子部门A1、A2的数据。
实现方式:
业务实体对象的归属部门 包含在 指定的若干个部门列表中
且 当前用户拥有的用户组 包含 指定的某个具体的用户组

还需要额外说明的一点是实际业务需求情况会复杂多变,非必要应避免引入新的数据权限控制维度,而是基于现有的维度做一些变通与转换。例如一个销售经理带两个销售员,要求销售经理可以查看自己和下属的销售订单,这时候不建议引入新的数据权限控制维度,如销售员与销售经理这种人员的上下级关系。优先考虑是否能够转换成现有控制维度来实现,例如,将销售经理和他的团队放到一个同一部门(可能是虚拟的组织机构)下,通过组织机构这个权限控制维度来实现。

技术实现

基于上述业务场景分析,我们需要做以下工作:

  1. 筛选器调用后端服务获取实体属性列表时,自动添加一个 当前用户组 属性作为筛选条件
  2. 约定自定义变量的含义与占位符,并在数据权限拦截处理的SQL片段中将其替换为运行时获取到的真实值
    • 当前用户 {@CurrentUser@}
    • 当前用户部门 {@CurrentDepartment@}
    • 当前用户组 {@CurrentUserGroup@}
  3. 复用数据筛选器的级联框展现控件类型,用来处理组织机构选择和用户组选择

‍‍

组织机构维度控制

业务实体对象的归属部门,可能有两种操作,一种是等于当前用户部门;另一种是在组织结构树中选择多个,结合用户组来控制。为了统一操作,可以将组织机构设置为级联框,然后将当前用户部门虚拟为根节点,对应值为{@CurrentDepartment@}。

这里有个小问题,对于数据字典和级联框,都需要调用后端服务获取数据源,但二者共享getConditions这个方法,并且这个方法只有一个参数,没法区分到底是数据字典还是级联框。
考虑到使用级联框仅有组织机构和用户组两类特定的数据,因此可以固化其编码,优先判断,不匹配时则就是数据字典了。

技术验证

同样,先拿先前的demo页面进行功能验证和调试。
筛选条件中增加部门,同步新增一个对应的操作,如下:

调整获取数据的方法getConditions,如下:

运行,整体效果出来了,如下:

不过这里有个关键问题,当选择了父级后,所有子级会自动清空,这样的话我们去判断是否包含就麻烦了,不仅需要判断集合中的值,而且还要判断集合中值的所有子部门,包括子部门的下级……

问题出在筛选器组件封装CASCADE的时候没有单独设置checkStrictly属性,而是复用了multiple属性,源码如下:

在进行数据权限控制的场景下,大多数情况下也是符合预期的,一般情况下,会在某个水平层级来控制数据权限,很少会跨越多个不同层级,因为从逻辑上就比较混乱,不利于运维。因此接受筛选器当前现状,只进行直接的集合包含关系的判定,不考虑子级。

正式实现

技术验证通过,我们将其迁移到正式环境下。
首先,前后端交互的视图对象EntityModelPropertyForFilterVO,新增两个属性,集合类型与是否多选属性。

其次,在筛选器组件调用的后端服务的地方,增加对组织机构相关属性的处理,如下:

此外,平台组织机构控制器中增加了将组织机构数据转换为级联框需要的数据结构,使用了递归,如下:

 /*** 获取级联框数据** @return*/@GetMapping("/cascader")@PreAuthorize("hasPermission(null,'system:organization:query')")public ResponseEntity<Result> cascader() {QueryWrapper<Organization> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(Organization::getStatus, StatusEnum.NORMAL.toString());// 附加按照排序号排序queryWrapper.orderByAsc(TableFieldConstant.DEFAULT_SORT_FILED);List<Organization> list = organizationService.list(queryWrapper);Organization rootOrganization = list.stream().filter(x -> x.getOrganization().equals(TreeDefaultConstant.DEFAULT_TREE_ROOT_PARENT_ID)).findFirst().get();CascaderItemVO root = convert2CascadeVO(rootOrganization);root.setChildren(convertToCascaderData(rootOrganization.getId(), list));return ResultUtil.success(root);}/*** 转换为级联数据** @param parentId         父级标识* @param organizationList 组织机构列表* @return 列表<cascader项目vo>*/private List<CascaderItemVO> convertToCascaderData(String parentId, List<Organization> organizationList) {List<CascaderItemVO> cascaderData = new ArrayList<>();List<Organization> subOrganizationList = organizationList.stream().filter(x -> x.getOrganization().equals(parentId)).collect(Collectors.toList());for (Organization organization : subOrganizationList) {CascaderItemVO cascaderItem = convert2CascadeVO(organization);List<CascaderItemVO> children = convertToCascaderData(organization.getId(), organizationList);cascaderItem.setChildren(children);cascaderData.add(cascaderItem);}if (CollectionUtils.isNotEmpty(cascaderData)) {return cascaderData;} else {return null;}}/*** 转换为级联框视图对象*/private CascaderItemVO convert2CascadeVO(Organization entity) {CascaderItemVO vo = new CascaderItemVO();vo.setValue(entity.getId());vo.setLabel(entity.getName());return vo;}

再次,前端发起调用上面的后端服务,以及拿到数据后的处理,如下:

运行,效果如下:

测试后续步骤,如下:

发现in没起作用,检查后是因为IN应该大写,调整后如下:

in语句能解析了,不过最后生成的SQL片段不对,多了引号和中括号,调整原来的代码,如下:

调整后,生成了正确的sql,如下:
(organizaiton IN (‘1186911361171308545’,‘1186911884368789506’))

增加当前部门

前面在组织机构功能调试正常的基础上,我们增加先前梳理的特殊控制:当前用户所属部门。
通过虚拟一个当前用户部门的节点,与平台的组织机构树并列,对应值为{@CurrentDepartment@},该部分工作在前端完成,如下:

实现效果如下:

注意,这里系统没有进行严格控制,从操作上用户可以混选虚拟的“当前用户部门”和组织机构中的数据。因为是基于筛选器组件自身功能扩展存在局限性,进行二选一控制实现起来比较麻烦,不过这点影响很小。主要在于进行数据权限配置的是系统管理员而不是业务用户,因此从操作上可以遵循二选一的模式,并不需要非得由系统来严格控制。

继续后续测试,生成规则正常,转换SQL片段也正常,如下:

虽然在用户当前部门模式下使用等于更合理,不过出于简洁的考虑,复用集合下的in也没什么问题,执行结果是一样的,至于性能差异,可能有一点点。

然后我们修改Mybatisplus数据权限处理器,将自己约定的{@CurrentDepartment@}占位符,替换为运行环境下的用户部门,如下:


查看后端最终执行的SQL语句,可以看到,正确附加了当前用户部门:
SELECT COUNT(*) AS total FROM cfg_template WHERE delete_flag = ‘NO’ AND (organizaiton IN (‘1186911361171308545’))

用户组维度控制

用户组维度的控制与组织机构维度控制高度相似,不展开赘述,只说差异点,主要有以下几点:

  1. 需要在左侧,筛选条件中增加一个虚拟的条件,当前用户拥有的用户组
  2. 右侧用户组是单选而不是多选
  3. 左侧用户当前拥有的用户组是一个集合,需要包含右侧选择的用户组

针对这几点差异来说说设计与实现。

前端在调用后端服务,拿到当前实体的模型属性后,在最后追加虚拟的条件,如下:

增加新的操作集合Contain,如下:

后端增加对包含操作符CT的处理,如下:


效果如下:

为用户组实现级联框数据转换,效果如下:


接下来是占位符的替换工作了,略复杂一些,但是也好实现。
无非是将当前用户拥有的用户组列表,转换成sql语句的in结构后替换掉占位符,如下:


最终(‘99’ IN (‘{@CurrentUserGroup@}’))替换后成为了标准的sql片段,如下:
AND (‘99’ IN (‘99’, ‘1’))

用户维度控制

用户控制维度相比前面两个要简单得多。当实体模型属性为用户时,比如平台预设的创建人和更新人属性,操作集合可以只设置一个等于,然后值为我们预设的特殊占位符{@CurrentUser@},在数据权限处理器中,获取当前运行的用户后,将占位符替换为真实的值。

整体思路就是上面说的,接下来简单列列要做的工作。

首先,获取实体属性转换为筛选器查询条件环节,增加用户属性的处理:

对应前端操作集合为:

在getConditions中增加相应的逻辑分支,如下:

这里虚拟了一个下拉列表选项,注意写法,调用筛选器的方法,是一个对象,这个对象需要有一个data属性,且该属性是一个数组。

最后,替换占位符,如下:

运行效果如下:

实际测试,查看控制台输出的sql,符合预期。

此外,这里的值设定,使用的是下拉列表的方式,主要还是考虑复用组件自身的功能。还考虑过一种实现方案,即设定一个新的操作类型,就叫“等于当前用户”,然后值控件设置为none(不显示)。但是这样新增的这个操作类型,与先前定义的等于、大于、包含等就不是同一维度的分类了,逻辑上显得混乱,因此未使用该方案。

总结

至此,我们完成了一二三开发平台的数据权限的设计与实现,通过配置,可以灵活的进行多维度的数据权限控制,包括用户、组织机构、用户组以及业务实体属性,控制对象为各个业务实体,无耦合,无侵入。

开源平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:[csdn专栏]
开源地址:[Gitee]
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~
请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/882464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB代码】指纹定位方法(KNN)介绍与例程(二维、轨迹定位),源代码可复制粘贴到MATLAB上运行

文章目录 指纹定位指纹定位技术简介基本原理位置估算公式1. 最近邻居算法(KNN)2. 加权最近邻居算法(W-KNN)3. 最小二乘法(LS)最终位置 P P P通过求解下面的方程获得:应用场景优缺点优点缺点总结源代码代码运行运行方法运行结果指纹定位 指纹定位技术简介 指纹定位是一…

SpringBoot日常:封装redission starter组件

文章目录 逻辑实现POM.xmlRedissionConfigRedissionPropertiesRedissionUtilsspring.factories 功能测试application.yml配置POM.xmlTestController运行测试 本章内容主要介绍如何通过封装相关的redission连接配置和工具类&#xff0c;最终完成一个通用的redission starter。并…

洛谷 P1038 [NOIP2003 提高组] 神经网络(拓扑排序)

题目传送门 感觉这道题需要我们高超的语文阅读水平…… 解题思路 我们发现要计算一个细胞的状态值&#xff08;&#xff09;&#xff0c;就需要先算出有边指向它的其他细胞对答案的贡献&#xff1b; 这是有拓扑序的&#xff0c;所以我们想到拓扑排序。 题目中说了&#xff0…

FastDFS单节点部署

FastDFS单节点部署 1、FastDFS入门1.1 分布式文件系统1.2 FastDFS 简介1.3 FastDFS 发展历史1.4 FastDFS 整体架构1.5 FastDFS 线上使用者 2、FastDFS 环境搭建2.1 FastDFS 安装2.1.1 安装前的准备2.1.2 安装 libfastcommon库2.1.3 安装 FastDFS 2.2FastDFS 配置2.2.1 去掉/etc…

即使是编程新手,也能利用ChatGPT编写高质量的EA

在外汇交易领域&#xff0c;MetaTrader是一款备受欢迎的交易软件&#xff0c;包括MT5和MT4&#xff0c;提供了众多强大的分析工具和自动化交易功能。对于没有编程经验的新手而言&#xff0c;编写专家顾问&#xff08;EA&#xff09;可能显得既复杂又令人望而却步。幸运的是&…

数据结构——链表,哈希表

文章目录 链表python实现双向链表复杂度分析 哈希表&#xff08;散列表&#xff09;python实现哈希表哈希表的应用 链表 python实现 class Node:def __init__(self, item):self.item itemself.next Nonedef head_create_linklist(li):head Node(li[0])for element in li[1…

spring6 IoC容器详解

目录 一、引言 示例开发 示例分析 启用Log4j2日志框架 使用方式 二、IoC容器 IoC容器 1.控制反转&#xff08;IoC&#xff09; 2.依赖注入 3.IoC容器在Spring的实现 基于XML管理Bean 依赖注入之setter注入 依赖注入之构造器注入 Bean生命周期 基于xml自动装配 …

基于SSM+微信小程序的打印室预约管理系统(打印2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的打印室预约管理系统实现了管理员和用户两个角色。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;附近打印店管理&#xff0c;文件打印管理&#xff0c;当…

ui自动化知识点-web端

UI : User Interface( ⽤户接⼝ - ⽤户界⾯ ) ,主要包括: app 、 web ui ⾃动化测试:使⽤⼯具或代码执⾏⽤例的过程 什么样的项⽬适合做⾃动化:1、需要回归测试项⽬(甲⽅⾃营项⽬、⾦融、电商)2、需求变动不频繁:稳定的模块3、项⽬周期⻓的项⽬:(甲⽅⾃营项⽬、6个…

YOLO11改进 | 主干网络 | 将backbone替换为Swin-Transformer结构【论文必备】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文给大家带来的教程是将YOLO11的backb…

LaTeX教程(016)-LaTeX文档结构(16)

LaTeX教程(016)- LaTeX \LaTeX LATE​X文档结构(16) 接上一讲 我们前面知道&#xff0c;\vref是对\ref的升级&#xff0c;而varioref包也提供了一个对\pageref升级的命令\vpageref。它和\vref的原理很相似&#xff0c;内置了一些判断。 \vpageref[same-page][other-page]{ke…

【C++】C++当中的复合类型——引用和指针

C当中的复合类型 最近开始系统地学习 C 的语法&#xff0c;参考的主要资料来自于 C Primer 第五版&#xff0c;对于学习过程中所遇到的较难理解的点&#xff0c;我会以blog的形式对问题和内容进行记录&#xff0c;并进行进一步地探讨。 这一部分的内容对应于参考资料 C Prime…

spring-cloud-alibaba-nacos-config2023.0.1.*启动打印配置文件内容

**背景&#xff1a;**在开发测试过程中如果可以打印出配置文件的内容&#xff0c;方便确认配置是否准确&#xff1b;那么如何才可以打印出来呢&#xff1b; spring-cloud-alibaba-nacos-config 调整日志级别 logging:level:com.alibaba.cloud.nacos.configdata.NacosConfigD…

Linux操作系统与windows无法相互复制问题

请先看完此文在进行操作&#xff01;&#xff01;&#xff01; 对于无法复制我们逐层分析&#xff1a; 1.为什么无法复制是不是少了什么工具(open-vm-tools-destop) 上网查阅可以看到如下 2.在此之前我的虚拟机装完Ubuntu 16.04的linux系统无法进行apt update(参考一下) li…

华三服务器R4900 G5在图形界面使用PMC阵列卡(P460-B4)创建RAID,并安装系统(中文教程)

环境以用户需求安装Centos7.9&#xff0c;服务器使用9块900G硬盘&#xff0c;创建RAID1和RAID6&#xff0c;留一块作为热备盘。 使用笔记本通过HDM管理口&#xff08;&#xff09;登录 使用VGA&#xff08;&#xff09;线连接显示器和使用usb线连接键盘鼠标&#xff0c;进行窗…

excel判断某一列(A列)中的数据是否在另一列(B列)中

如B列如果有7个元素&#xff0c;在A列右边的空白列中&#xff0c;输入如下公式&#xff1a; COUNTIF($B$1:$B$7,A1), 其中&#xff0c;$B$1:$B$7代表A列中的所有数据即绝对范围&#xff0c;A1代表B列中的一个单元格.

Servlet(一)

一.什么是servlet Servlet 是一种实现动态页面的技术。 是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app。 1.回顾 动态页面 vs 静态页面 静态页面也就是内容始终固定的页面。即使 用户不同/时间不同/输入的参数不同 , 页面内容也不会发生变化。(除…

从 Microsoft 官网下载 Windows 10

方法一&#xff1a; 打开 Microsoft 官网&#xff1a; 打开开发人员工具&#xff08;按 F12 或右键点击“检查”&#xff09;。 点击“电脑模拟手机”按钮&#xff0c;即下图&#xff1a; 点击后重新加载此网页&#xff0c;即可看到下载选项。

Palo Alto Networks Expedition 未授权SQL注入漏洞复现(CVE-2024-9465)

0x01 产品介绍&#xff1a; Palo Alto Networks Expedition 是一款强大的工具&#xff0c;帮助用户有效地迁移和优化网络安全策略&#xff0c;提升安全管理的效率和效果。它的自动化功能、策略分析和可视化报告使其在网络安全领域中成为一个重要的解决方案。 0x02 漏洞描述&am…

windows下安装、配置neo4j并服务化启动

第一步&#xff1a;下载Neo4j压缩包 官网下载地址&#xff1a;https://neo4j.com/download-center/ &#xff08;官网下载真的非常慢&#xff0c;而且会自己中断&#xff0c;建议从以下链接下载&#xff09; 百度网盘下载地址&#xff1a;链接&#xff1a;https://pan.baid…