本文章记录使用spring-boot结合mybatis实现简单的数据权限控制,前端逻辑不做概述,主要实现逻辑是使用mybatis自带的方法获取执行的SQL,然后根据存储的数据权限方案拼接where条件,最后拼接到待执行的SQL后面实现数据权限过滤。
	本文章涉及知识:spring注解、AOP、mybatis-plus
  1. 项目结构
    依赖
 <!-- mybatis-plus begin -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
  1. 配置
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package eimsp.database.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import eimsp.config.ConfigValueUtil;
import eimsp.database.data.DataSourceContextHolder;
import eimsp.util.DataSourceUtil;
import eimsp.util.DbConUtil;
import eimsp.util.DbTableConModel;
import eimsp.util.JdbcUtil;
import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

@Configuration
@ComponentScan(
    basePackages = {"eimsp"}
)
@MapperScan(
    basePackages = {"eimsp.*.mapper"},
    sqlSessionTemplateRef = "sqlSessionTemplate"
)
public class MybatisPlusConfig {
    static final String ALIASES_PACKAGE = "eimsp.*.entity";
    @Autowired
    private DataSourceUtil dataSourceUtil;
    @Autowired
    private ConfigValueUtil configValueUtil;

    public MybatisPlusConfig() {
    }

    @Primary
    @Bean(
        name = {"dataSourceSystem"}
    )
    public DataSource dataSourceOne(Environment env) {
        String prefix = "spring.datasource.druid.";
        return this.druidDataSource(env, prefix);
    }

//数据拦截代码
    @Bean(
        name = {"sqlSessionFactorySystem"}
    )
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceSystem") DataSource dataSource, Interceptor[] interceptors) throws Exception {
    //创建拦截代码
        return this.createSqlSessionFactory(dataSource, interceptors);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        if (Boolean.parseBoolean(this.configValueUtil.getMultiTenancy())) {
            DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
            HashMap<String, TableNameHandler> map = new HashMap(150);
            String url = this.dataSourceUtil.getUrl().replace("{dbName}", this.dataSourceUtil.getDbInit());
            Connection conn = JdbcUtil.getConn(this.dataSourceUtil.getUserName(), this.dataSourceUtil.getPassword(), url);
            List<DbTableConModel> dbTableModels = new ArrayList();
            if (conn != null) {
                if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.MYSQL.getDb())) {
                    dbTableModels = DbConUtil.mysqlgetList(conn, this.dataSourceUtil.getDbInit());
                } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.SQL_SERVER.getDb())) {
                    dbTableModels = DbConUtil.sqlServergetList(conn, this.dataSourceUtil.getDbInit());
                } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.ORACLE.getDb())) {
                    dbTableModels = DbConUtil.orcalgetList(conn, this.dataSourceUtil.getDbInit());
                }
            }

            Iterator var7 = ((List)dbTableModels).iterator();

            while(var7.hasNext()) {
                DbTableConModel dbTableModel = (DbTableConModel)var7.next();
                if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.MYSQL.getDb())) {
                    map.put(dbTableModel.getTable(), (sql, tableName) -> {
                        return DataSourceContextHolder.getDatasourceName() + "." + dbTableModel.getTable();
                    });
                } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.SQL_SERVER.getDb())) {
                    map.put(dbTableModel.getTable(), (sql, tableName) -> {
                        return DataSourceContextHolder.getDatasourceName() + ".dbo." + dbTableModel.getTable();
                    });
                } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.ORACLE.getDb())) {
                    map.put(dbTableModel.getTable().toLowerCase(), (sql, tableName) -> {
                        return DataSourceContextHolder.getDatasourceName().toUpperCase() + "." + dbTableModel.getTable();
                    });
                }
            }

            dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map);
            interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        }

        if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.MYSQL.getDb())) {
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.SQL_SERVER.getDb())) {
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER));
        } else if (this.dataSourceUtil.getDataType().toLowerCase().equals(DbType.ORACLE.getDb())) {
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
        }

        return interceptor;
    }

    protected DataSource druidDataSource(Environment env, String prefix) {
        String url;
        String dbNull;
        if (Boolean.parseBoolean(this.configValueUtil.getMultiTenancy())) {
            dbNull = env.getProperty(prefix + "dbnull");
            url = env.getProperty(prefix + "url").replace("{dbName}", dbNull);
        } else {
            dbNull = env.getProperty(prefix + "dbname");
            url = env.getProperty(prefix + "url").replace("{dbName}", dbNull);
        }

        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(env.getProperty(prefix + "username"));
        dataSource.setUrl(url);
        dataSource.setPassword(env.getProperty(prefix + "password"));
        dataSource.setDriverClassName(env.getProperty(prefix + "driver-class-name"));
        return dataSource;
    }

    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<String> mapperLocations = new ArrayList();
        mapperLocations.add("classpath*:mapper/*/*.xml");
        List<Resource> resources = new ArrayList();
        if (mapperLocations != null) {
            Iterator var4 = mapperLocations.iterator();

            while(var4.hasNext()) {
                String mapperLocation = (String)var4.next();

                try {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException var7) {
                }
            }
        }

        return (Resource[])resources.toArray(new Resource[resources.size()]);
    }
//数据拦截代码
    public SqlSessionFactory createSqlSessionFactory(DataSource dataSource, Interceptor[] interceptors) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setMetaObjectHandler(new MybatisPlusMetaObjectHandler());
        bean.setGlobalConfig(globalConfig);
        bean.setVfs(SpringBootVFS.class);
        bean.setTypeAliasesPackage("eimsp.*.entity");
        bean.setMapperLocations(this.resolveMapperLocations());
        bean.setConfiguration(this.configuration(interceptors));
        return bean.getObject();
    }
//数据拦截代码
    public MybatisConfiguration configuration(Interceptor[] interceptors) {
        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        mybatisConfiguration.setMapUnderscoreToCamelCase(true);
        mybatisConfiguration.setCacheEnabled(false);

        for(int i = interceptors.length - 1; i >= 0; --i) {
            Interceptor item = interceptors[i];
            mybatisConfiguration.addInterceptor(item);
        }

        mybatisConfiguration.setLogImpl(Slf4jImpl.class);
        mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL);
        return mybatisConfiguration;
    }

    @Bean
    public IKeyGenerator keyGenerator() {
        return new H2KeyGenerator();
    }

    @Bean
    public ISqlInjector sqlInjector() {
        return (builderAssistant, mapperClass) -> {
        };
    }
}

  1. 数据权限注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataPermission {
    String methods() default "";
}

AOP代码

@Aspect
@Component
public class DataScopeAspect {
    public DataScopeAspect() {
    }

    @Pointcut("@annotation(eimsp.database.annotation.DataPermission)")
    public void dataPermissionPointCut() {
    }

    @Before("dataPermissionPointCut()")
    public void doBefore(JoinPoint point) throws Throwable {
        this.handleDataPermission(point);
    }

    protected void handleDataPermission(final JoinPoint joinPoint) {
        SessionUtil sessionUtil = (SessionUtil)SpringContext.getBean(SessionUtil.class);
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            DataPermission controllerDataScope = (DataPermission)method.getAnnotation(DataPermission.class);
            sessionUtil.setDataPermissionMethods(controllerDataScope.methods());
        }
    }
}

拦截器逻辑实现

@Intercepts({@Signature(
    type = StatementHandler.class,
    method = "prepare",
    args = {Connection.class, Integer.class}
)})
@Component
public class DataPermissionInterceptor extends AbstractSqlParserHandler implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(DataPermissionInterceptor.class);

    public Object intercept(Invocation invocation) throws Throwable {
        String moduleId = ServletUtil.getHeader("ModuleId");
        if (moduleId == null) {
            return invocation.proceed();
        } else {
            UserProvider userProvider = (UserProvider)SpringContext.getBean(UserProvider.class);
            UserInfo userInfo = userProvider.get();
            if (userInfo.getIsAdministrator()) {
                return invocation.proceed();
            } else {
                StatementHandler statementHandler = (StatementHandler)PluginUtils.realTarget(invocation.getTarget());
                MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
                this.sqlParser(metaObject);
                MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
                if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
                    return invocation.proceed();
                } else {
                    SessionUtil sessionUtil = (SessionUtil)SpringContext.getBean(SessionUtil.class);
                    String dataPermissionMethods = sessionUtil.getDataPermissionMethods();
                    List<String> methods = Lists.newArrayList(dataPermissionMethods.split(","));
                    String methodName = mappedStatement.getId();
                    if (Strings.isNullOrEmpty(dataPermissionMethods)) {
                        return invocation.proceed();
                    } else if (methods.stream().anyMatch((it) -> {
                        return methodName.contains(it);
                    })) {
                        BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
                        String originalSql = boundSql.getSql();
                        Object parameterObject = boundSql.getParameterObject();
                        AuthorizeService authorizeService = (AuthorizeService)SpringContext.getBean(AuthorizeService.class);
                        String whereSql = authorizeService.GetConditionSql(userInfo, moduleId);
                        if (StringUtil.isEmpty(whereSql)) {
                            whereSql = "1 = 2";
                        }

                        Select select = (Select)CCJSqlParserUtil.parse(originalSql);
                        Expression sourceWhere = ((PlainSelect)select.getSelectBody()).getWhere();
                        whereSql = sourceWhere != null ? sourceWhere.toString() + whereSql : whereSql.replaceFirst("AND|OR", "");
                        Expression where = CCJSqlParserUtil.parseCondExpression(whereSql);
                        ((PlainSelect)select.getSelectBody()).setWhere(where);
                        originalSql = select.toString();
                        metaObject.setValue("delegate.boundSql.sql", originalSql);
                        return invocation.proceed();
                    } else {
                        return invocation.proceed();
                    }
                }
            }
        }
    }

    public Object plugin(Object target) {
        return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
    }

    public void setProperties(Properties properties) {
    }

    public DataPermissionInterceptor() {
    }
}
GitHub 加速计划 / sp / spring-boot
37
6
下载
spring-projects/spring-boot: 是一个用于简化Spring应用开发的框架。适合用于需要快速开发企业级Java应用的项目。特点是可以提供自动配置、独立运行和内置的Tomcat服务器,简化Spring应用的构建和部署。
最近提交(Master分支:23 天前 )
e2ba4dad - 23 小时前
7a595f1b Closes gh-47333 1 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐