数据权限实现

 

在项目实际开发中我们不光要控制一个用户能访问哪些资源,还需要控制用户只能访问资源中的某部分数据。

控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC,但是控制用户只能访问某部分资源(即我们常说的数据权限)使用RBAC模型是不够的,本文我们尝试在RBAC模型的基础上融入数据权限的管理控制。

首先让我们先看下RBAC模型。

一、RBAC模型

RBAC是Role-BasedAccess Control的英文缩写,意思是基于角色的访问控制。

RBAC事先会在系统中定义出不同的角色,不同的角色拥有不同的权限,一个角色实际上就是一组权限的集合。而系统的所有用户都会被分配到不同的角色中,一个用户可能拥有多个角色。使用RBAC可以极大地简化权限的管理。

RBAC模型还可以细分为RBAC0,RBAC1,RBAC2,RBAC3。这里我们不讨论他们之间的差异,感兴趣的同学可以自行研究,我们主要聚焦于常见的RBAC0模型上。

如下图就是一个经典RBAC0模型的数据库设计。

 

 

rbac0模型

在RBAC模型下,系统只会验证用户A是否属于角色RoleX,而不会判断用户A是否能访问只属于用户B的数据DataB。这种问题我们称之为“水平权限管理问题”。

数据权限

列表数据权限,主要通过数据权限控制行数据,让不同的人有不同的查看数据规则;要实现数据权限,最重要的是需要抽象出数据规则。

数据规则

比如我们系统的商机数据,需要从下面几个维度来控制数据访问权限。

  1. 销售人员只能看自己的数据;
  2. 各大区的销售经理只能看各区域的数据(安徽大区的销售经理看安徽区域的商机数据),同理也适用于某BG分管领导只能看所在BG的商机数据;
  3. 财务人员只能看金额小于一万的数据。

上面的这些维度就是数据规则。

这样数据规则的几个重点要素我们也明晰了,就是规则字段,规则表达式,规则值,上面三个场景对应的规则分别如下:

  1. 规则字段:创建人,规则表达式:= ,规则值:当前登录人
  2. 规则字段:所属大区,规则表达式:= ,规则值:安徽大区
  3. 规则字段:销售金额,规则表达式:< ,规则值:10000

 

 

数据规则

规则字段配置说明: 
条件表达式:大于/大于等于/小于/小于等于/等于/包含/模糊/不等于
规则值:指定值 ( 固定值/系统上下文变量 )

关联资源、用户

光有数据规则是不够的,我们还需要把数据规则跟资源和用户进行绑定。

数据规则与资源的绑定很简单,我们只需要建立一个中间表即可,如下图所示:

 

 

关联资源与用户

这样资源就可以关联上了数据规则。

在应用设计上我们需要一个单独的数据规则管理功能,方便我们录入数据规则,然后在资源管理页面(比如商机列表)上就可以选择内置的数据规则进行资源与规则的绑定。

那么如何让不同的用户拥有不同的数据规则呢?

在RBAC模型中,用户是通过授予不同的角色来进行资源的管理,同理我们可以让角色在授予权限的时候关联上数据规则,这样最终在系统上就体现为不同的用户拥有不同的数据规则。

有点拗口,我们还是按上面的例子来说。

销售人员、大区销售经理、财务人员属于不同的角色,他们都拥有商机列表这个资源权限,但是在给这些角色绑定商机列表资源权限时我们可以勾选对应的数据规则(上面已经实现资源与数据规则的绑定)。体现在数据库设计中我们可以在角色资源对应关系表 Role_Permission中添加一个字段用于存储关联的数据规则,如果有多个数据规则可以使用分隔符分割。

最终RBAC模型演变成如下所示的模型:

 

 

按照上面的设计我们需要区分各个大区管理的数据权限则需要建立不同的大区角色,如安徽大区销售经理、上海大区销售经理,然后分别给角色勾选对应的数据规则。这里就类似于RBAC1中的角色继承的概念了。

这样我们就基本实现了RBAC与数据规则的绑定,但是我们还有个问题就是如何在系统中落地。

这里我们就要借助大名鼎鼎的AOP来实现了,这篇文章只讲原理不讲实现,所以我们只顺带提一下实现方案。

  1. 自定义一个数据权限的注解,比如叫PermissionData
  2. 在对应的资源请求方法,比如商机列表上添加自定义注解@PermissionData
  3. 利用AOP抓取到用户对应角色的所有数据规则并进行SQL拼接,最终在SQL层面实现数据过滤。

继续优化

在上面的设计中我们通过给不同角色绑定不同数据规则实现了数据权限,但是考虑下面一种场景:某角色需要看到的数据范围为 “所属大区为安徽大区且事业部为消费者事业部的商机数据”,在这种场景里按照我们之前的设计需要建立两个数据规则:

  1. 所属大区 = 安徽大区
  2. 所属事业部 = 消费者事业部

然后再建立2个不同的角色,分别授予不同的数据规则,如果这样的场景比较多的话很容易出现角色爆炸的情况,所以我们这里再抽取出 数据规则组 的概念。

一个数据规则组有多个数据规则,数据规则之间通过 AND 进行连接,放一张应用设计图:

 

 

数据规则

体现在数据库设计中就变成了如下所示:

 

 

通过上面8张表的设计我们实现了RBAC模型与数据权限的结合,当然这里还有继续优化的空间。比如这里的规则字段和规则值我们可以抽取出对应的字典表,让数据规则表去关联这些字典字段,这样在应用层配置数据规则的时候就不需要管理员手动填写而是从字典项中去选择了,减少了数据规则配置出错的概率。

 

二、数据权限模型

数据模型是基于传统的RBAC模型来设计的,由于我们这里的应用场景不一样,所以这里的数据权限模型并没有严格按照上面的方案来设计,但是万变不离其宗,核心原理还是相同的。

首先我来介绍一下我们最终实现的效果

实现效果

 

 

一个组件(可以理解成菜单)可以绑定多个授权维度,当给角色授权组件时可以给这个授权组件赋予不同维度的权限。

关于数据权限的授权维度有以下几个关键点需要仔细体会:

  1. 给一个角色勾选授权维度实际上是在限制这个角色所能看到的数据范围

  2. 任何一个授权维度勾选了"全部",相当于不限制此维度的权限。

    如果一个角色勾选了客户群的全部 + A产品线,那么最终生成的sql 会是 where 产品线 in ('A产品线')

  3. 如果一个角色勾选了多个维度,维度之间用 AND 拼接

    如果一个角色勾选了A客户群 + B产品线,那么最终生成的sql 会是 where 客户群 in('A客户群')AND 产品线 in ('B产品线')

  4. 一个用户可能有多个角色,角色之间用 OR 拼接

    一个用户有两个角色:客户群总监,产品线经理。其中客户群总监角色拥有A客户群和B客户群的权限,产品线经理角色拥有A产品线权限,那么最终生成的sql会是 where 客户群 in ('A客户群','B客户群') OR 产品线 in ('A产品线')

由于我们业务场景中数据规则比较单一,全部使用 in作为sql条件连接符,你们可以根据实际业务场景进行补充。

数据模型

最终的数据模型如下所示:

 

 

这里的组件大家完全可以理解成RBAC模型中的资源、菜单,只不过叫法不同而已。

数据权限表结构

下面是具体的表结构设计

授权维度表

CREATE TABLE `wb_dimension` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '维度编码',
  `DIMENSION_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '维度名称',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授权维度'

具体授权维度表(产品线)

CREATE TABLE `wb_dimension_proc_line` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '维度编码',
  `PROC_LINE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '产品线编码',
  `PROC_LINE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '产品线名称',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授权维度-产品线'

跟授权维度表实际是一个表继承的关系,由于每个授权维度的属性不一样,展现形式也不一样,所以分表存储。

组件路由表

CREATE TABLE `wb_route` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键ID',
  `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '组件ID',
  `ROUTE_URL` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由地址',
  `AUTHORIZATION_TYPE` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '授权方式:1 自定义,2 上下级授权, 3 范围授权',
  `AUTHORIZATION_DIMENSION` json DEFAULT NULL COMMENT '授权维度',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='组件路由'

当组件属性授权方式为范围授权时在应用侧会强制要求选择具体的授权维度,如 产品线、客户群。

角色表

CREATE TABLE `wb_role` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键ID',
  `ROLE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色CODE',
  `ROLE_NAME` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色名称',
  `IDENTITY_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '身份ID'
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色表'

角色上有一个身份属性,多个角色可以归属同一个身份,方便对角色进行分类管理。

角色组件绑定表

CREATE TABLE `role_component_relation` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键ID',
  `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色ID',
  `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '组件ID',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色授权组件'

角色组件授权规则表(核心)

CREATE TABLE `wb_role_component_rule` (
  `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色ID',
  `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '组件ID',
  `RULE_CODE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '规则编码',
  `RULE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '规则名称',
  `RULE_CONDITION` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '规则条件',
  `RULE_VALUE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '规则值',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色组件维度规则表'

数据权限的核心表,规则条件的取值为IN,规则值存储具体的维度编码,当在数据维度中选择 全部 时我们将规则值存储为ALL这个特殊值,方便后续生成SQL语句。

 

 

实现过程

上面提到过数据权限的实现过程,这里再回顾一下

  1. 自定义一个数据权限的注解,比如叫DataPermission
  2. 在对应的资源请求方法,比如商机列表上添加自定义注解@DataPermission
  3. 利用AOP抓取到用户对应角色的所有数据规则并进行SQL拼接,最终在SQL层面实现数据过滤。

代码实现

自定义数据权限注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface DataPermission {
 /**
  * 数据权限类型
  * 1 上下级授权  2 数据范围授权
  */
 String permissionType() default "2";
 
 /**
  * 配置菜单的组件路径,用于数据权限
  */
 String componentRoute() default "";
}

定义数据权限处理切面

@Aspect
@Slf4j
public class DataPermissionAspect {

 @Autowired
 private RoleComponentRuleService roleComponentRuleService;

 @Pointcut("@annotation(com.ifly.workbench.security.annotation.DataPermission)")
 public void pointCut() {
  
 }
 
 @Around("pointCut()")
 public Object around(ProceedingJoinPoint point) throws  Throwable{

  HttpServletRequest request = SpringContextUtils.getHttpServletRequest();

  //获取请求token
  String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
  String userName = JwtUtil.getUsername(token);
    
  MethodSignature signature = (MethodSignature) point.getSignature();
  Method method = signature.getMethod();
  DataPermission permissionData = method.getAnnotation(DataPermission.class);

    //获取授权方式
  String permissionType = permissionData.permissionType();
  //获取组件路由
  String componentRoute = permissionData.componentRoute();
    
    if (StringUtils.isNotEmpty(componentRoute)){
   // 查找当前用户此组件下的所有规则
   List<RoleComponentRuleDTO> componentRules = roleComponentRuleService.getRoleComponentRule(userName, componentRoute);
      
   if(CollectionUtils.isNotEmpty(componentRules)){
     DataPermissionUtils.installDataSearchConditon(request, componentRules);
     SysUserCacheInfo userInfo = buildCacheUser(userName);
     DataPermissionUtils.installUserInfo(request, userInfo);
    }
  }
    
    return  point.proceed();
 }

 private SysUserCacheInfo buildCacheUser(String userName) {
  SysUserCacheInfo info = new SysUserCacheInfo();
  info.setSysUserName(userName);
  info.setOneDepart(true);
  return info;
 }
}

在AOP中获取当前用户、需要访问的组件中所有的数据规则,参考wb_role_component_rule表设计,并将其放到Request作用域中。

数据权限工具类

public class DataPermissionUtils {
 
 public static final String COMPONENT_DATA_RULES = "COMPONENT_DATA_RULES";
 
 public static final String SYS_USER_INFO = "SYS_USER_INFO";


   /**
  * 往链接请求里面,传入数据查询条件
  * @param request
  * @param componentRules
  */
 public static void installDataSearchConditon(HttpServletRequest request, List<RoleComponentRuleDTO> componentRules) {
  // 1.先从request获取MENU_DATA_AUTHOR_RULES,如果存则获取到LIST
  List<RoleComponentRuleDTO> list = loadDataSearchCondition();

  if (list==null) {
   // 2.如果不存在,则new一个list
   list = Lists.newArrayList();
  }
  list.addAll(componentRules);
  // 3.往list里面增量存指
  request.setAttribute(COMPONENT_DATA_RULES, list);
 }

 /**
  * 获取请求对应的数据权限规则
  *
  */
 @SuppressWarnings("unchecked")
 public synchronized List<RoleComponentRuleDTO> loadDataSearchCondition() {
  return (List<RoleComponentRuleDTO>) SpringContextUtils.getHttpServletRequest().getAttribute(COMPONENT_DATA_RULES);
    
 }
 
 public synchronized void installUserInfo(HttpServletRequest request, SysUserCacheInfo userinfo) {
  request.setAttribute(SYS_USER_INFO, userinfo);
 }
}

在Request中存储数据规则。

查询组件规则

public interface RoleComponentRuleService extends IService<RoleComponentRule> {

    /**
     * 根据 用户域账户和组件编码 获取组件对应的关系
     *
     * @param userName      域账号
     * @param componentCode 组件编码
     * @return 用户的所有规则
     */
    List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode);
}
@Service
public class RoleComponentRuleServiceImpl extends ServiceImpl<RoleComponentRuleMapper, RoleComponentRule> implements RoleComponentRuleService {

    @Resource
    private RoleComponentRuleMapper roleComponentRuleMapper;

    /**
     * 根据 用户域账户和组件编码 获取组件对应的关系
     * @param userName      域账号
     * @param componentCode 组件编码
     * @return 用户的所有规则
     */
    @Override
    public List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode) {
        return roleComponentRuleMapper.getRoleComponentRule(userName,componentCode);
    }

}
<select id="getRoleComponentRule" resultType="com.ifly.vo.RoleComponentRuleDTO">
 SELECT
    tab1.id,
    tab1.role_id,
    tab4.role_code,
    tab1.component_id,
    tab1.rule_code,
    tab1.rule_name,
    tab1.rule_condition,
    tab1.rule_value,
    tab4.identity_id
  FROM
   wb_role_component_rule tab1
   LEFT JOIN user_role_relation tab2 ON tab2.role_id = tab1.role_id
   LEFT JOIN wb_component tab3 ON tab3.id = tab1.component_id
   LEFT JOIN wb_role tab4 ON tab4.id = tab1.role_id
   JOIN role_component_relation tab5 ON tab5.role_id = tab1.role_id
   AND tab5.component_id = tab1.component_id
  where
   tab2.user_account = #{userName}
   and tab3.component_code = #{componentCode}
</select>

Controller调用

@ApiOperation(value = "服务BU-领导-总览")
@GetMapping("opp/getLeaderOverviewSve")
@DataPermission(componentRoute = "020202")
public Result<SalesProjOverviewSve> getLeaderOverviewSve(@RequestParam(name = "identityId") String identityId) {
 String permissionSql = RuleQueryGenerator.getPermissionSql(identityId);
  log.info("查服务BU-领导-总览-permissionSQL==" + permissionSql);

 return Result.OK(overviewSveService.getLeaderOverviewSve(permissionSql));
}

在controller的请求方法上加上自定义注解@DataPermission并指定组件编码,然后通过工具类生成SQL条件,最后将SQL条件传入service层进行处理。

构建数据权限SQL

@Slf4j
@UtilityClass
public class RuleQueryGenerator {

    private static final String SQL_AND = " and ";

    private static final String SQL_OR = " or ";

    private static final String SQL_JOINT = " (%s) ";

    /**
     * 获取带有数据权限的SQL
     * @param identityId 身份ID
     */
    public String getPermissionSql(String identityId) {
        //------------------------获取当前身份的数据规则------------------------------------
        List<RoleComponentRuleDTO> conditionList = getCurrentIdentyPermission(identityId);
        if (CollectionUtils.isEmpty(conditionList)) {
            //没有权限
            return "1 = 0";
        }
        //存在权限
        //对当前身份根据规则编码分组-去除不同角色中相同编码且规则值为ALL的规则 并根据角色id分组
        Map<String, List<RoleComponentRuleDTO>> ruleMap = getRuleMapByRoleId(conditionList);

        StringBuilder sb = new StringBuilder();
        String roleSql;
        if (MapUtils.isNotEmpty(ruleMap)) {
            //按角色拼接SQL
            for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : ruleMap.entrySet()) {

                List<RoleComponentRuleDTO> lists = entry.getValue();

                // 同角色之间使用 AND
                roleSql = buildRoleSql(lists);

                //角色之间使用 OR
                if (StringUtils.isNotEmpty(roleSql)) {
                    jointSqlByRoles(sb, roleSql);
                }
            }

        }
        return sb.toString();
    }

    private static List<RoleComponentRuleDTO> getCurrentIdentyPermission(String identityId) {
        //----------------------------获取所有数据规则-----------------------------
        List<RoleComponentRuleDTO> roleRuleList = DataPermissionUtils.loadDataSearchCondition();
        if(CollectionUtils.isEmpty(roleRuleList)){
            return null;
        }
        //-----------------------------过滤掉不属于当前身份的规则-----------------------------------
        return roleRuleList.stream()
                .filter(item -> item.getIdentityId().equals(identityId))
                .collect(Collectors.toList());
    }

    /**
     * 构建单角色SQL
     */
    private static String buildRoleSql(List<RoleComponentRuleDTO> lists) {
        StringBuilder roleSql = new StringBuilder();
        for (RoleComponentRuleDTO item : lists) {
            //如果出现全选 则 代表全部,不需要限定范围
            if ("ALL".equals(item.getRuleValue())) {
                continue;
            }
            //将规则转换成SQL
            String filedSql = convertRuleToSql(item);

            roleSql.append(SQL_AND).append(filedSql);
        }
        return roleSql.toString();
    }


    /**
     * 将单一规则转化成SQL,默认全部使用 In
     * ruleCode : area_test
     * ruleValue : 区域1,区域2,区域3
     * @param rule 规则值
     */
    private static String convertRuleToSql(RoleComponentRuleDTO rule) {
        String whereCondition = " in ";
        String ruleValueConvert = getInConditionValue(rule.getRuleValue());
        return rule.getRuleCode() + whereCondition + ruleValueConvert;
    }


    /**
     * IN字符串转换
     * 区域1, 区域2, 区域3  --> ("区域1","区域2","区域3")
     * 江西大区  --> ("江西大区")
     */
    private static String getInConditionValue(String ruleValue) {
        String[] temp = ruleValue.split(",");
        StringBuilder res = new StringBuilder();
        for (String string : temp) {
            res.append(",'").append(string).append("'");
        }
        return "(" + res.substring(1) + ")";
    }

    /**
     * 拼接单角色的SQL
     * @param sqlBuilder 总的SQL
     * @param roleSql    单角色SQL
     */
    private static void jointSqlByRoles(StringBuilder sqlBuilder, String roleSql) {
        roleSql = roleSql.replaceFirst(SQL_AND, "");
        if (StringUtils.isEmpty(sqlBuilder.toString())) {
            sqlBuilder.append(String.format(SQL_JOINT, roleSql));
        } else {
            sqlBuilder.append(SQL_OR).append(String.format(SQL_JOINT, roleSql));
        }
    }

    /**
     *
     * 1. 对当前身份根据规则编码分组-去除不同角色中相同编码且规则值为ALL的规则
     * 2. 对角色进行分组
     * @param conditionList 数据规则
     * @return 分组后的规则list
     */
    private static Map<String, List<RoleComponentRuleDTO>> getRuleMapByRoleId(List<RoleComponentRuleDTO> conditionList) {
    //--------过滤掉不属于当前身份的规则,并对条件编码进行分组-----------------------------------
    Map<String, List<RoleComponentRuleDTO>> conditionMap = conditionList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRuleCode));

  //--------相同编码分组中存在ALL的排除掉-----------------------------------------------
    List<RoleComponentRuleDTO> newRoleRuleList = new ArrayList<>();
    if (MapUtils.isNotEmpty(conditionMap)) {
     for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : conditionMap.entrySet()) {
       boolean flag = true;
        List<RoleComponentRuleDTO> lists = entry.getValue();
        for (RoleComponentRuleDTO item : lists) {
         if ("ALL".equals(item.getRuleValue())) {
           flag = false;
            break;
          }
        }
        
        if (flag) {
           newRoleRuleList.addAll(lists);
        }
      }
     }
     if (CollectionUtils.isNotEmpty(newRoleRuleList)) {
        return newRoleRuleList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRoleId));
      }
     return Maps.newHashMap();
    }
}

核心类,用于生成数据权限查询的SQL脚本。

Dao层实现

<select id="getLeaderOverviewSve" resultType="com.ifly.center.entity.SalesProjOverviewSve">
 SELECT <include refid="column_list"/>  FROM U_STD_ADS.LTC_SALES_PROJ_OVERVIEW_SVE
  <where>
   <if test="permissionSql != null and permissionSql != ''">
    ${permissionSql}
    </if>
  </where>
</select>

Dao层接受service层传入已经生成好的sql语句,作为查询条件直接拼接在业务语句之后。

小结

以上,就是数据权限的实现过程,其实代码实现并不复杂,主要还是得理解其中的实现原理。如果你也有数据权限的需求,不妨参考一下。当然如果你有更好的实现方案,也可以留言告诉我。

 鸣谢:
https://mp.weixin.qq.com/s/jFceD2K-ccIiA1wvxZNUyw
posted @ 2022-08-04 15:59  春光牛牛  阅读(987)  评论(0编辑  收藏  举报