Mybatis拦截器实现数据权限的示例代码

网友投稿 323 2022-08-21


Mybatis拦截器实现数据权限的示例代码

在我们日常开发过程中,通常会涉及到数据权限问题,下面以我们常见的一种场景举例:

一个公司有很多部门,每个人所处的部门和角色也不同,所以数据权限也可能不同,比如超级管理员可以查看某张

表的素有信息,部门领导可以查看该部门下的相关信息,部门普通人员只可以查看个人相关信息,而且由于角色的

不同,各个角色所能查看到的数据库字段也可能不相同,那么此处就涉及到了数据权限相关的问题。那么我们该如

何处理数据权限相关的问题呢?我们提供一种通过Mybatis拦截器实现的方式,下面我们来具体实现一下

pom.xml依赖

org.springframework.boot

spring-boot-starter-parent

2.1.13.RELEASE

1.8

3.2.0

org.springframework.boot

spring-boot-starter-web

mysql

mysql-connector-java

com.baomidou

mybatis-plus-boot-starter

${mybatis-plus.version}

org.projectlombok

lombok

application.yml文件

server:

port: 80

spring:

application:

name: data-scope

datasource:

url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

username: root

password: 123456

druid:

# 验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效

validation-query: SELECT 1

# 连接是否被空闲连接回收器(如果有)进行检验. 如果检测失败, 则连接将被从池中去除

test-while-idle: true

# 是否在从池中取出连接前进行检验, 如果检验失败, 则从池中去除连接并尝试取出另一个

test-on-borrow: true

# 是否在归还到池中前进行检验

test-on-return: false

# 连接在池中最小生存的时间,单位是毫秒

min-evictable-idle-time-millis: 30000

#mybatis配置

mybatis-plus:

type-aliases-package: com.mk.entity

mapper-locations: classpath:mapper/**/*.xml

global-config:

db-config:

id-type: auto

field-strategy: not_empty

logic-delete-value: 1

logic-not-delete-value: 0

configuration:

map-underscore-to-camel-case: true

cache-enabled: false

call-setters-on-nulls: true

log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

代码实现

DataScode.java

@Data

public class DataScope {

// sql过滤条件

StrUFSJNFYAAming sqlCondition;

// 需要过滤的结果字段

String[] filterFields;

}

MybatisPlusConfig.java

@Configuration

public class MybatisPlusConfig {

@Bean

@ConditionalOnMissingBean

public DataScopeInterceptor dataScopeInterceptor() {

return new DataScopeInterceptor();

}

@Bean

public PaginationInterceptor paginationInterceptor() {

PaginationInterceptor page = new PaginationInterceptor();

page.setDialectType(DbType.MYSQL.getDb());

return page;

}

@Bean

public ConfigurationCustomizer mybatisConfigurationCustomizer(){

return new ConfigurationCustomizer() {

@Override

public void customize(MybatisConfiguration configuration) {

configuration.setObjectWrapperFactory(new MybatisMapWrapperFactory());

}

};

}

}

DataScopeInterceptor.java

@Slf4j

@Intercepts({@Signature(type = Executor.class, method = "query",

args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class})})

public class DataScopeInterceptor implements Interceptor {

@Override

public Object intercept(Invocation invocation) throws Throwable {

log.info("执行intercept方法:{}", invocation.toString());

Object[] args = invocation.getArgs();

MappedStatement ms = (MappedStatement) args[0];

Object parameterObject = args[1];

// 查找参数中包含DataScope类型的参数

DataScope dataScope = findDataScopeObject(parameterObject);

if (dataScope == null) {

return invocation.proceed();

}

if (!ObjectUtils.isEmpty(dataScope.getSqlCondition())) {

// id为执行的mapper方法的全路径名,如com.mapper.UserMapper

String id = ms.getId();

// sql语句类型 select、delete、insert、update

String sqlCommandType = ms.getSqlCommandType().toString();

// 仅拦截 select 查询

if (!sqlCommandType.equals(SqlCommandType.SELECT.toString())) {

return invocation.proceed();

}

BoundSql boundSql = ms.getBoundSql(parameterObject);

String origSql = boundSql.getSql();

log.info("原始SQL: {}", origSql);

// 组装新的 sql

String newSql = String.format("%s%s%s%s", "select * from (", origSql, ") ", dataScope.getSqlCondition());

// 重新new一个查询语句对象

BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,

boundSql.getParameterMappings(), boundSql.getParameterObject());

// 把新的查询放到statement里

MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));

for (ParameterMapping mapping : boundSql.getParameterMappings()) {

String prop = mapping.getProperty();

if (boundSql.hasAdditionalParameter(prop)) {

newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));

}

}

Object[] queryArgs = invocation.getArgs();

queryArgs[0] = newMs;

log.info("改写的SQL: {}", newSql);

}

Object result = invocation.proceed();

return handleReslut(result, Arrays.asList(dataScope.getFilterFields()));

}

/**

* 定义一个内部辅助类,作用是包装 SQL

*/

class BoundSqlSqlSource implements SqlSource {

private BoundSql boundSql;

public BoundSqlSqlSource(BoundSql boundSql) {

this.boundSql = boundSql;

}

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();

}

@Override

public Object plugin(Object target) {

log.info("plugin方法:{}", target);

if (target instanceof Executor) {

return Plugin.wrap(target, this);

}

return target;

}

@Override

public void setProperties(Propertihttp://es properties) {

// 获取属性

// String value1 = properties.getProperty("prop1");

log.info("properties方法:{}", properties.toString());

}

/**

* 查找参数是否包括DataScope对象

*

* @param parameterObj 参数列表

* @return DataScope

*/

private DataScope findDataScopeObject(Object parameterObj) {

if (parameterObj instanceof DataScope) {

return (DataScope) parameterObj;

} else if (parameterObj instanceof Map) {

for (Object val : ((Map, ?>) parameterObj).values()) {

if (val instanceof DataScope) {

return (DataScope) val;

}

}

}

return null;

}

public Object handleReslut(Object returnValue, List filterFields){

if(returnValue != null && !ObjectUtils.isEmpty(filterFields)){

if (returnValue instanceof ArrayList>){

List> list = (ArrayList>) returnValue;

List newList = new ArrayList();

if (1 <= list.size()) {

for(Object object:list){

if (object instanceof Map) {

Map map = (Map) object;

for (String key : filterFields) {

map.remove(key);

}

newList.add(map);

} else {

newList.add(decrypt(filterFields, object));

}

}

returnValue = newList;

}

} else {

if (returnValue instanceof Map) {

Map map = (Map) returnValue;

for (String key : filterFields) {

map.remove(key);

}

} else {

returnValue = decrypt(filterFields, returnValue);

}

}

}

return returnValue;

}

public static T decrypt(List filterFields, T t) {

Field[] declaredFields = t.getClass().getDeclaredFields();

try {

if (declaredFields != null && declaredFields.length > 0) {

for (Field field : declaredFields) {

if (filterFields.contains(field.getName())) {

field.setAccessible(true);

field.set(t, null);

field.setAccessible(false);

}

}

}

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

return t;

}

}

SalariesMapper.xml

SELECT * from salaries where salary between #{start} and #{end}

select * from salaries where emp_no = #{empNo} limit 0,1

SalariesMapper.java

@Mapper

public interface SalariesMapper extends BaseMapper {

List pageList(DataScope dataScope, @Param("start") int start, @Param("end") int end, Page page);

Map getByEmpNo(DataScope dataScope, @Param("empNo") int empNo);

}

SalariesService.java

@Service

public class SalariesService extends ServiceImpl {

@Autowired

private SalariesMapper salariesMapper;

public List getList(){

Page page = new Page<>(1, 10);

DataScope dataScope = new DataScope();

// 设置查询条件

dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'");

// 将结果集过滤掉salary和toDate字段

dataScope.setFilterFields(new String[]{"salary", "toDate"});

return salariesMapper.pageList(dataScope, 60000, 70000, page);

}

public Map getByEmpNo() {

DataScope dataScope = new DataScope();

// 将结果集过滤掉salary和toDate字段

dataScope.setFilterFields(new String[]{"salary", "toDate"});

return salariesMapper.getByEmpNo(dataScope, 10001);

}

}

启动服务,执行相关操作,sql在执行之前会执行DataScopeInterceptor拦截器中的逻辑,从而改变sql,具体的

相关操作就是将原来的sql语句origSql在外层包装一层过滤条件,如:select * from (origSql) 过滤条件,

此处的过滤条件要封装到DataScope对象中,例如:dataScope.setSqlCondition("s where 1=1 and

s.emp_no = '10001'") , 那么在经过拦截器处理以后要执行的sql语句为

select * from (origSql) s where 1=1 and s.emp_no = '10001', 从而实现数据权限相操作,当然此处的过滤条件只是为了演示效果举的一个例子

而已,在实际开发过程中要根据用户角色等等设置具体的过滤条件。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Mybatis 连接mysql数据库底层运行的原理分析
下一篇:Java Spring的核心与设计思想你知道吗
相关文章

 发表评论

暂时没有评论,来抢沙发吧~