spring项目+mysql等关系型数据库实现数据权限思路

一、前言

     鉴权一般分为两种: 功能鉴权和数据鉴权。我们这里先暂且只讨论数据鉴权。

     我们在做一般的后台管理系统时(crm、cms等) ,可能还会遇到如下业务场景: 

         某CRM管理系统向公司的全部销售人员提供服务,其中用户可能不只有普通的销售人员可能还会有销售主管、销售经理等。

         1、普通销售人员只能看到自己的客户信息

         2、销售主管可以看到整个小组的客户信息

         3、经理可以看到整个部门的客户信息

    这时数据权限就显得格外重要。

二、实现思路

        1、实现前提需要有一套比较完善的RBAC体系

        2、当发起查询请求时确保可以拿到当前登录用户的组织架构信息和角色等信息

        3、关键步骤 - 拿到用户的组织架构信息拼接到sql查询条件中

三、实现

       1、我们可以自定义一个注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {

}

      2、然后对该注解进行aop处理,也就是说在这里需要收集当前用户的组织架构信息

@Slf4j
@Aspect
@Component
@Order(2)
public class DataPermissionAspect {

    private static ThreadLocal<String> ORG_SQL_THREAD_LOCAL = new ThreadLocal<>();

    @Around(value = "@annotation(DataPermission)")
    @SneakyThrows
    public Object around(ProceedingJoinPoint pjp){
        
       //这里来获取用户的组织架构信息并set到threadLocal(偷个懒哈哈)
    }
}

       3、然后我们自定义一个mybatis拦截器

@Slf4j
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
@Component
public class SqlInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            Executor executor = (Executor) invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            //由于逻辑关系,只会进入一次
            if (args.length == 4) {
                //4 个参数时
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            log.info("------ 原始的sql 为 : {}", boundSql.getSql());
            String originalSql = boundSql.getSql();
            //这里进行组织架构条件拼接,当然还需要处理排序或者没有条件等情况完成拼接(我这里随便写一个 1 = 1)
            boolean order_by = originalSql.contains("ORDER BY");
            String newSql = !order_by ? originalSql + " AND 1 = 1" : originalSql;
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            MappedStatement newMs = newMappedStatement(ms, new MyBoundSqlSqlSource(newBoundSql));
            for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            }
            return executor.query(newMs, parameter, rowBounds, resultHandler, cacheKey, newBoundSql);
        } catch (Exception e) {

        }
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 定义一个内部辅助类,作用是包装 SQL
     */
    public static class MyBoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public MyBoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }
}

4、将上面的mybatis拦截器配置到mybatis的SqlSessionFactory中

@Configuration
public class MyMybatisConfig {
    //这里注入的就是步骤3中拦截器
    @Resource
    SqlInterceptor sqlInterceptor;

    @Resource
    SqlSessionFactory sqlSessionFactory;

    @PostConstruct
    public void add() {
        sqlSessionFactory.getConfiguration().addInterceptor(sqlInterceptor);
    }
}

四、结论

        经本人实测,上述方案是可行的。当运行sql时可以动态的将sql拼接起来。

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
摘 要 在如今这个人才需求量大的时代,各方企业为了永葆企业的活力与生机,在不断开 拓进取的同时,又广泛纳用人才,为企业的长久发展奠定了基础。于是,各个企业与部 门机构,都不可避免地会接触到人力资源管理的问题。 Hrm 是一款人力资源管理系统,其主要功能模块有员工个人信息修改、请假、员工 的薪资管理、考勤管理、社保管理。其中考勤管理实现了员工考勤状态的修改与员工考 勤月报表的导出,以及通过员工考勤记录的导入来实现员工考勤状态的判断。社保管理, 主要实现了员工社保的计算以及明细的修改。薪资管理,实现了员工工资的调整,以及 员工月工资报表的导出。 本项目采用了前后端分离的技术,前端是基于 Vue+ElementUI+Axios 开发的,后端 则是基于 Spring Boot+MyBatis Plus+ Jwt+Mysql。本项目实现权限菜单管理,通过员 工的权限动态渲染菜单,并动态生成路由。通过 Jwt token 来判断当前登录的员工以及 员工的登录状态。 关键词:人力资源管理系统,Spring Boot,Vue,权限管 人力资源管理是企业运营中必不可少的一环,它关系到企业的前途与发展。尤其对 于中小微企业来说,对企业的发展有着举足轻重的作用。随着近年来,政府对创业项目 的大力扶持,我国创业型企业蓬勃发展。据统计,2019 年,我国创业企业数量已达 1810 万余家,占全国企业数的 97%,截止 2020 年,我国创业企业数量达到了 2030 万,同比 增长 10%。虽然我国创业企业的基数在不断增大,但是能够长久存活的企业却少之又少。 在创业初期,随着企业初具规模,大多数创业者开始将主要精力集中在市场调研和 开发产品上,而忽略了团队的内部管理。据调查,中国企业的平均寿命是 7.02 年,但 70%的企业存活不超过 5 年,究其原因有很多,其中最重要的一点就是,人力资源管理 未能有效推动企业向前发展

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值