基于mybatis-plus interceptor的数据权限以及自定义injector整合baseMapper

文章介绍了如何在MybatisPlus框架下自定义Interceptor和Injector来实现数据权限功能。通过DataScope类定义数据范围,DataScopeType枚举定义权限类型,然后在Interceptor中处理SQL,根据权限动态添加查询条件。同时,自定义了通用Mapper接口和方法,如selectListByScope,以支持带数据权限的基础操作。最后,通过配置类将自定义拦截器和注入器加入到系统中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.自定义interceptor做数据权限

  1. 自定义dataScope
@Data
@EqualsAndHashCode(callSuper = true)
public class DataScope extends HashMap {
    /**
     * 限制范围的字段名称 (除个人外)
     */
    private String scopeName = "org_id";
    /**
     * 限制范围为个人时的字段名称
     */
    private String selfScopeName = "user_id";

    private String selfName = "create_by";
    /**
     * 当前用户ID
     */
    private Integer userId;

    /**
     * 具体的数据范围
     */
    private List<Long> orgIds;

    private DataPermissionType Type;
    /**
     * 限制范围为插入时的类型
     */
    private String dataType = "type";

}

@Getter
@AllArgsConstructor
@ApiModel(value = "DataScopeType", description = "数据权限类型-枚举")
public enum DataScopeType implements BaseEnum {

    /**
     * ALL=5全部
     */
    ALL(5, "全部"),
    /**
     * THIS_LEVEL=4本级
     */
    THIS_LEVEL(4, "本级"),
    /**
     * THIS_LEVEL_CHILDREN=3本级以及子级
     */
    THIS_LEVEL_CHILDREN(3, "本级以及子级"),
    /**
     * CUSTOMIZE=2自定义
     */
    CUSTOMIZE(2, "自定义"),
    /**
     * SELF=1个人
     */
    SELF(1, "个人"),
    ;

    @ApiModelProperty(value = "描述")
    private final int val;

    private final String desc;


    public static DataScopeType match(String val, DataScopeType def) {
        return Stream.of(values()).parallel().filter((item) -> item.name().equalsIgnoreCase(val)).findAny().orElse(def);
    }

    public static DataScopeType match(Integer val, DataScopeType def) {
        return Stream.of(values()).parallel().filter((item) -> val != null && item.getVal() == val).findAny().orElse(def);
    }

    public static DataScopeType get(String val) {
        return match(val, null);
    }

    public static DataScopeType get(Integer val) {
        return match(val, null);
    }

    public boolean eq(final DataScopeType val) {
        return val != null && eq(val.name());
    }

    @Override
    @ApiModelProperty(value = "编码", allowableValues = "ALL,THIS_LEVEL,THIS_LEVEL_CHILDREN,CUSTOMIZE,SELF", example = "ALL")
    public String getCode() {
        return this.name();
    }
    public static DataScopeType getEnum(String desc) {
        for (DataScopeType value : DataScopeType.values()) {
            if (value.getDesc().equals(desc)) {
                return value;
            }
        }
        return null;
    }
    public static String getType(Integer code) {
        DataScopeType[] values = DataScopeType.values();
        for (DataScopeType value : values) {
            if (code.equals(value.getCode())) {
                return value.getDesc();
            }
        }
        return "";
    }

}
  1. 自定义Interceptor
@Slf4j
@AllArgsConstructor
public class DataScopeInnerInterceptor implements InnerInterceptor {
    private final Function<Integer, Map<String, Object>> function;


    /**
     * 查找参数是否包括DataScope对象
     *
     * @param parameterObj 参数列表
     * @return DataScope
     */
    protected Optional<DataScope> findDataScope(Object parameterObj) {
        if (parameterObj == null) {
            return Optional.empty();
        }
        if (parameterObj instanceof DataScope) {
            return Optional.of((DataScope) parameterObj);
        } else if (parameterObj instanceof Map) {
            for (Object val : ((Map<?, ?>) parameterObj).values()) {
                if (val instanceof DataScope) {
                    return Optional.of((DataScope) val);
                }
            }
        }
        return Optional.empty();
    }
    /**
    *其原理就是sql外包装一层查询
    **/    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        DataScope dataScope = findDataScope(parameter).orElse(null);
        if (dataScope == null) {
            return;
        }

        String originalSql = boundSql.getSql();

        String scopeName = dataScope.getScopeName();
        String selfScopeName = dataScope.getSelfScopeName();
        String typeScopeName = dataScope.getDataType();
        DataPermissionType dataType = dataScope.getType();
        Integer userId = dataScope.getUserId() == null ? ContextUtil.getUserId().intValue() : dataScope.getUserId();
        List<Long> orgIds = dataScope.getOrgIds();
        DataScopeType dsType = DataScopeType.SELF;

        if (CollectionUtil.isEmpty(orgIds)) {
            //查询当前用户的 角色 最小权限
            //userId

            //dsType orgIds
            Map<String, Object> result = function.apply(userId);
            if (result == null) {
                return;
            }

            Integer type = (Integer) result.get("dsType");
            dsType = DataScopeType.get(type);
            orgIds = (List<Long>) result.get("orgIds");
        }
            //查全部
            if (DataScopeType.ALL.eq(dsType)) {
                return;
            }
            //查个人
            if (DataScopeType.SELF.eq(dsType)) {
                originalSql = "select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + dataScope.getSelfName() + " = " + userId;
            }
            //查其他
            else if (StrUtil.isNotBlank(scopeName) && CollUtil.isNotEmpty(orgIds)) {
                String join = CollectionUtil.join(orgIds, ",");
                originalSql = "select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + scopeName + " in (" + join + ")";
            }



        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(originalSql);
    }

}
  1. 添加自定义interceptor进mybatis
protected List<InnerInterceptor> getPaginationBeforeInnerInterceptor() {
    List<InnerInterceptor> list = new ArrayList<>();
    Boolean isDataScope = databaseProperties.getIsDataScope();
    if (isDataScope) {
        // todo UserService.class 改方法是查询当前用户的组织以及角色权限等信息
        list.add(new DataScopeInnerInterceptor(userId -> userServiceApi.getDataScope(userId)));
    }
    return list;
}

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
List<InnerInterceptor> beforeInnerInterceptor = getPaginationBeforeInnerInterceptor();
if (!beforeInnerInterceptor.isEmpty()) {
    beforeInnerInterceptor.forEach(interceptor::addInnerInterceptor);
}
  1. 添加interceptor进入sqlSessionFactory

@Configuration
public class DataSourceConfiguration {

    //会依赖注入Spring容器中所有的mybatis的Interceptor拦截器
    @Autowired(required = false)
    private Interceptor[] interceptors;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    @Bean
    public SqlSessionFactory dataHubSqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisConfiguration configuration = new MybatisConfiguration();
//        configuration.addInterceptor(paginationInterceptor());

        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setConfiguration(configuration);
        sqlSessionFactoryBean.setDataSource(dataSource);
        //如果自定义sqlFactoryBean加入这个自定义的mybatis拦截器才会生效
        sqlSessionFactoryBean.setPlugins(interceptors);
        //设置globalConfig自定义注入器才会生效
        sqlSessionFactoryBean.setGlobalConfig(getGlobalConfig());
        return sqlSessionFactoryBean.getObject();
    }
    @Bean
    public GlobalConfig getGlobalConfig() {

        if (this.applicationContext.getBeanNamesForType(MySqlInjector.class, false, false).length > 0 ) {
            LampSqlInjector lampSqlInjector = applicationContext.getBean(MySqlInjector.class);
            GlobalConfig globalConfig = new GlobalConfig().setSqlInjector(mySqlInjector);
            return globalConfig;
        }
        return new GlobalConfig();
    }

}

这种情况如果使用mybatis-plus框架无法使用其baseMapper的一些基础方法比如selectList等等,我们可以自定义mybaits-plus injector来实现

2.自定义injector配合数据权限使用

  1. 首先自定义通用Mapper
public interface SuperMapper<T> extends BaseMapper<T> {

    /**模仿BaseMapper做的带数据权限查询的接口
     * @Author sff
     * @Description
     * @Date 13:57 2021/9/9
     * @Param [queryWrapper, dataScope]
     * @Return java.util.List<T>
     */
    List<T> selectListByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope dataScope);

    /**
     * 通用数据权限分页查询
     *
     * @Author sff
     * @Description
     * @Date 17:19 2021/9/6
     * @Param [page, queryWrapper, dataScopeType]
     * @Return E
     */
    <E extends IPage<T>> E selectPageByScope(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope dataScope);
}
  1. 自定义selectListByScepe
/**
 * 相当于selectList的重载,加了数据权限
 * @Author sff
 * @Description
 * @Date 14:45 2021/9/9
 */
public class SelectListByScope extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        //给sql脚本赋值
        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        //给sql脚本赋值
        String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
                sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, "selectListByScope", sqlSource, tableInfo);

    }

}

public class SelectPageByScope extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_PAGE;
        String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true),
                tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, "selectPageByScope", sqlSource, tableInfo);
    }
}

MappedStatement对象:里封装了XML当中一个标签的所有信息,主要用于保存配置的SQL语句。
示例: insert into c_table_permission (data_id, org_id, is_query, is_visible) values (#{item.tableId}, #{orgId}, #{item.isQuery}, #{item.isVisible})

3. 自定义mybtis注入器

public class MySqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);

        //增加自定义方法
        methodList.add(new SelectListByScope());
        methodList.add(new SelectPageByScope());
        return methodList;
    }
}
  1. 增加配置类将SQL注入器添加到容器
  @Bean    
  @ConditionalOnMissingBean    
  public MySqlInjector getMySqlInjector() {
          return new MySqlInjector();
 }
  1. 重复interceptor步骤(如果自定义了sqlSessionFactoryBean则需要手动放到sqlSessionFactory容器中)

@Configuration
public class DataSourceConfiguration {

    //会依赖注入Spring容器中所有的mybatis的Interceptor拦截器
    @Autowired(required = false)
    private Interceptor[] interceptors;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    @Bean
    public SqlSessionFactory dataHubSqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisConfiguration configuration = new MybatisConfiguration();
//        configuration.addInterceptor(paginationInterceptor());

        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setConfiguration(configuration);
        sqlSessionFactoryBean.setDataSource(dataSource);
        //如果自定义sqlFactoryBean加入这个自定义的mybatis拦截器才会生效
        sqlSessionFactoryBean.setPlugins(interceptors);
        //设置globalConfig自定义注入器才会生效
        sqlSessionFactoryBean.setGlobalConfig(getGlobalConfig());
        return sqlSessionFactoryBean.getObject();
    }
    @Bean
    public GlobalConfig getGlobalConfig() {

        if (this.applicationContext.getBeanNamesForType(MySqlInjector.class, false, false).length > 0 ) {
            LampSqlInjector lampSqlInjector = applicationContext.getBean(MySqlInjector.class);
            GlobalConfig globalConfig = new GlobalConfig().setSqlInjector(mySqlInjector);
            return globalConfig;
        }
        return new GlobalConfig();
    }

}

3.实际应用


@Service
public class EventServiceImpl extends ServiceImpl<EventMapper, Event> implements EventService {
     @Override
    public IPage<Event> permissionList(User user) {
        DataScope dataScope = new DataScope();
        dataScope.setUserId(user.getId());
        IPage<Event> eventIPage = new Page<>(1, 10);
        IPage<Event> events = baseMapper.selectPageByScope(eventIPage, Wraps.<Event>lbQ()
                .eq(Event::getWorkspaceId, user.getWorkspaceId())
                .orderByDesc(Event::getUpdateTime), dataScope);
 //       List<Event> eventList = baseMapper.selectListByScope(Wraps.<Event>lbQ().eq(Event::getWorkspaceId, user.getWorkspaceId()).orderByAsc(Event::getCreateBy), dataScope);
        return events;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值