一种灵活的数据权限思路(AOP、反射、MyBatis拦截器)

本文介绍了一种灵活的数据权限解决方案,利用AOP、反射和MyBatis拦截器,结合策略模式实现细粒度控制。通过自定义注解和页面配置规则,实现不同角色对不同数据的查看权限,如限制单位人员查看特定类型的企业,限制查看特定状态的订单等。同时提供一键代码生成,简化开发流程。
摘要由CSDN通过智能技术生成

图片

来源:juejin.cn/post/7267090979537944631

来源:juejin.cn/post/7308992638468227109

  • 1 前言

  • 2 需求

  • 3 设计思路

  • 4 例子1 查看订单金额大于100且小于500的订单

    • 规则配置

    • 代码

  • 5 例子2 查看收货人地址模糊查询钦南区的订单

    • 规则配置

    • 代码

  • 6 当然,一键代码生成,一句代码都不用写即可,实现单表的增删改查

    • EntityController

    • EntityService

    • EntityServiceImpl

    • 自定义注解

  • [7 项目地址 wonder-server: 一个有意思的权限管理系统2]

  • 参考资料


1 前言

我一年java,在小公司,当前公司权限这块都没有成熟的方案,目前我知道权限分为功能权限和数据权限,我不知道数据权限这块大家是怎么解决的,但在实际项目中我遇到数据权限真的复杂,你永远不知道业主在这方面的需求是什么。

我也有去搜索在这方面是怎么做,但是我在gitee、github搜到的权限管理系统他们都是这么实现的:查看全部数据自定义数据权限本部门数据权限本部门及以下数据仅本人数据权限,但是这种控制粒度完全不够的,所以就想自己实现一下。

2 需求

需求一 有一个单位企业的树,企业都是挂在某个单位下面的,企业是分类型的(餐饮企业经营企业生产企业),业主需要单位的人限定某些单位只能看一个或他指定的某个类型的企业。现在指定角色A只能查看餐饮经营企业,那就只能使用查看自定义部门数据这个,然后在10000家企业里面慢慢勾选符合的企业,这样可以是可以,但是我觉得这样做不太妥。

估计有人说:那你把三种类型的企业分组,餐饮企业挂在餐饮分组下,其他同理。然后用自定义数据权限选中那两个不就可以了吗?可以是可以,但是我不是业主,业主要求了那些企业必须挂在哪些单位下,在页面显示的树也不能显示什么餐饮企业分组生产企业... 说到底,除非你有办法改变业主的想法。

需求二 类似订单吧,角色A只能查看未支付的订单,角色B只能看交易金额在100~1000元的订单。

用通用的那5种权限对这两个需求已经是束手无策了。

3 设计思路

后来我看到一篇文章【数据权限就该这么实现(设计篇) [1]】,对我有很大的启发,从数据库字段下手,用规则来处理

图片

图片

我以这个文章的思路为基础,设计了这么一个关系

图片

图片

主要还是这张规则表,通过在页面配置好相关的规则来实现对某个字段的控制

CREATE TABLE `sys_rule` (
  `id` bigint NOT NULL,
  `remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '备注',
  `mark_id` bigint DEFAULT NULL,
  `table_alias` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '表别名',
  `column_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '数据库字段名',
  `splice_type` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '拼接类型 SpliceTypeEnum',
  `expression` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '表达式 ExpressionEnum',
  `provide_type` tinyint DEFAULT NULL COMMENT 'ProvideTypeEnum 值提供类型,1-值,2-方法',
  `value1` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '值1',
  `value2` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '值2',
  `class_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '全限定类名',
  `method_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '方法名',
  `formal_param` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '形参,分号隔开',
  `actual_param` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '实参,分号隔开',
  `create_time` datetime DEFAULT NULL,
  `create_by` bigint DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `update_by` bigint DEFAULT NULL,
  `deleted` bit(1) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='规则表';

整体思路就是通过页面来对特定的接口设置规则,如果提供类型是@DataScope注解用在方法上,那么默认机会在执行SQL前去拼接对应的数据权限。

如果提供类型是方法@DataScope注解用在方法上,那么会根据你配置的方法名参数类型去反射执行对应的方法,得到该规则能查看的所有idList,然后在执行SQL前去拼接对应的数据权限,这是默认的处理方式。如果@DataScope注解使用在形参上或者使用Service提供的方法接口,那么需要开发者手动处理,返回什么那么是开发者自定义了。

所以字段你自己定,联表也没问题、反射执行什么方法、参数是什么、过程怎么样也是你自己定,灵活性很高(至少我是这么认为的,哈哈哈哈哈哈)

新建 DataScopeHandler
@Component
public class DataScopeHandler implements DataPermissionHandler {

    Map<String, ExpressStrategy> expressStrategyMap = new HashMap<>();

    @PostConstruct
    public void init() {
        expressStrategyMap.put(ExpressionEnum.EQ.toString(), new EqStrategyImpl());
        expressStrategyMap.put(ExpressionEnum.NE.toString(), new NeStrategyImpl());
        // ....其他情况
    }

    @Override
    public Expression getSqlSegment(Expression oldWhere, String mappedStatementId) {
        DataScopeAspect.DataScopeParam dataScopeParam = DataScopeAspect.getDataScopeParam();
        // 没有规则就不限制
        if (dataScopeParam == null || dataScopeParam.getDataScopeInfo() == null || CollectionUtil.isEmpty(dataScopeParam.getDataScopeInfo().getRuleList()) || SecurityUtil.isAdmin()) {
            return oldWhere;
        }

        Expression newWhere = null;

        DataScopeInfo dataScopeInfo = dataScopeParam.getDataScopeInfo();
        List<RuleDto> ruleList = dataScopeInfo.getRuleList();
        for (RuleDto rule : ruleList) {
            ExpressStrategy expressStrategy = expressStrategyMap.get(rule.getExpression());
            if (expressStrategy == null)
                throw new IllegalArgumentException("错误的表达式:" + rule.getExpression());

            newWhere = expressStrategy.apply(rule, newWhere);
        }

        return oldWhere == null ? newWhere : new AndExpression(oldWhere, new Parenthesis(newWhere));
    }
}
使用策略模式 ExpressStrategy
public interface ExpressStrategy {

    Expression apply(RuleDto rule, Expression where);

    default Object getValue(RuleDto rule) {
        if (rule.getProvideType().equals(ProvideTypeEnum.METHOD.getCode())) {
            return rule.getResult();
        } else if (rule.getProvideType().equals(ProvideTypeEnum.VALUE.getCode())) {
            return rule.getValue1();
        } else {
            throw new IllegalArgumentException("错误的提供类型");
        }
    }

    default Column getColumn(RuleDto rule) {
        String sql = "".equals(rule.getTableAlias()) || rule.getTableAlias() =&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尘世中-迷途小书童

欢迎IT从业者的头脑风暴

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值