# data_power **Repository Path**: wuxi_zero/data_power ## Basic Information - **Project Name**: data_power - **Description**: 数据权限解决方案 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 0 - **Created**: 2019-04-23 - **Last Updated**: 2024-06-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 数据权限解决方案 #### 介绍 数据权限分为行级权限,和列级权限,本次重点讲解行级权限。 ### 技术活力来之分享,死于闭环。 ** ### Mybatis拦截器实现 ** ``` 解决问题:与业务层解耦,修改最终的sql。 原理:利用Mybatis的分页拦截器,这个拦截器是Mybatis优先级最高的拦截器, 在mybatis解析xml获取sql的时候,通过mybatis分页拦截器根据自定义数据权限规则产生权限sql, 拼接成最终sql。最后交给mybatis自己处理。 ``` ![输入图片说明](https://images.gitee.com/uploads/images/2019/0423/104926_92111476_4864477.png "未命名文件.png") > 缺点: > 需要部署到每个服务上,不太适合微服务。如果是单个服务的话可以使用相对麻烦发,不推荐使用 > 优点: > 与业务层完全解耦。 ** ### AOP技术 ** ``` 解决问题:通过Aop(后置通知)解决列级权限。 原理:利用springAOP实现在获取最终数据时结果过滤。 ``` 工作流程: ![输入图片说明](https://images.gitee.com/uploads/images/2019/0423/105111_5ece4738_4864477.png "屏幕截图.png") > 缺点: > 只能有效的解决列级权限,无法解决权限。 > > 优点: > 与业务层完全解耦。 ``` 解决问题:通过Aop(前置通知)解决行级权限。 原理:利用springAOP技术的前置通知,解析数据权限获取动态sql,放入缓存,在从缓存中获取动态sql当做条件使用。 ``` > 缺点: > 需要部署到每个服务上,不太适合微服务。如果是单个服务的话却很好。 > 优点: > 与业务层完全解耦。 ** ### 基于动态sql技术实现 ** ``` 解决问题:动态配置sql语句 原理:通过约定好的规范,前端提交相关配置,产生规则json。进行持久化处理。可以搭配aop进行灵活处理,也可作为一个单独服务来 ``` 配置数据规则这里有三个大的方面: **功能模块** 、 **数据资源** 、 **角色模块** 、 **数据资源** 、 **角色** ``` **功能模块** :为什么这里要加上功能模块的约束?是因为博主觉得我们某一个页面在查询数据的时候,会有一个查询的范围,比如订单查询页面肯定只能查询和订单有关联的实体功能,而不可能查询和它没有任何关联的业务。 **数据资源** :具体对哪种数据资源做数据权限,比如订单的状态不等于取消状态、订单的下单时间小于当前月份等等。 **角色** :数据资源的主体,还可以是用户、部门、组织等等。 ``` 优点:灵活,可以动态配置。与业务层完全分离,以接口的形式可选择的调用。 系统导图 数据权限流程图: ![输入图片说明](https://images.gitee.com/uploads/images/2019/0423/110226_34136253_4864477.png "屏幕截图.png") 数据权限同步流程: ![输入图片说明](https://images.gitee.com/uploads/images/2019/0423/110248_deab4c25_4864477.png "屏幕截图.png") ** ### 总结 ** 推荐使用基于动态sql技术实现,原因是因为我们服务是微服务的形式,我们要尽可能的减少对下游服务的修改,最好形式是由数据权限管理的服务来专门解决各个下游服务的数据权限问题。 数据权限规则: 通过上述我们最终得到权限规则字符串,一个类似这种json字符串(仅参考): ``` {"rules":[{"field":"xxx.UserName","operate":"eq","value":"java"}, {"field":"xxx.age","operate":"lt","value":"100"}], "logicoperate":"and"} ``` 分析: - field:字段名(可为接口展示的字段名,若为前端展示的字段名的话,需要做和数据库的映射,不推荐数据库中的字段,容易混淆) - xxx: 表示表名(为了解决多表连接时,同一字段名在不同表中等问题) - operate: 表示逻辑运算符,在字段和参数值之间。 - eq,lt,and : 表示数据逻辑运算符(eq 表示 = , lt 表示 < ), 建议使用公共方法来做映射。 - value:表示参数值 - logicoperate:表示一个where条件之间的逻辑运算符。 对多条件建议 ``` {"groups":[ {"rules":[{"field":"xxx.Order_Status","operate":"in","value":"[0,5,10]"},{"field":"xxx.Order_Type","operate":"eq","value":"1 "}],"logicoperate":"and"}, {"rules":[{"field":"xxx.LineName","operate":"eq","value":"fenzhuangxian"}]} ],"logicoperate":"or"} ``` - groups: 表示分组,多条件。 数据权限表结构(仅参考): ![输入图片说明](https://images.gitee.com/uploads/images/2019/0423/110924_c2f8092f_4864477.png "屏幕截图.png") ** ### 注意 ** : 用户角色和数据权限映射表中的 resource_url (请求资源路由地址),针对这个做一个特别说明: 我们这边路由规则是基于restful风格的形式,因此需要针对这种情况需要做特殊处理请,建议使用站位符来替换参数,推荐这个前端通过传参形式来做。 ``` 例如:****** /agent/getAgentName/{agentNo}这种形式 ``` agentNO为数字的话后端可以通过正则表达式来处理进行精确的站位判断。 但是如果为字符串的话,就会相对麻烦,需要我们强制定义路由规则,改动太大,不推荐使用,如不前端增加一个参数效率高。 及在HttpServletRequest的param中增加一个参数,只要约定好参数名就很好解决了, 及 url_name : "****** /agent/getAgentName/?", "?"为参数站位符。 注意点: 动态sql选择: ``` 例如:渠道商服务某接口A调用数据权限服务B接口的时候。返回的是一个动态的sql,而不是一个参数条件。 ``` 原因是:如果当做参数条件的时候会出现一种极端情况,比如:A服务中a是一个全局的参数(及页面上筛选条件),和B数据权限中条件产生冲突。 具体到sql语句: 不加数据权限时的sql:select * from A where money >90; 数据权限sql :此角色的数据权限是 money < 60 以参数的形式: 这样可能会出现数据有问题,最终的sql可能变成 select * from A where money < 60。 其实我们最终的结果应该是 select * from A where money >90 and money < 60 这里提现了使用动态的sql的好处了。 在mybatis中处理的动态sql也要注意一下: ``` 例如:不能直接使用 money < 60 写入到xml中,如: ``` rulePowerSql 就是 money < 60 ``` and #{rulePowerSql} ``` 原因是mybatis会将这个当做字符串,会解析成 select * from A where money >90 and "money < 60" ,出现错误。 需要转译: ``` and ``` 注意一定要用 $符号不能使用# 。#会mybatis会进行预编译,会出现错误。