Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限詳解
前言
在我們?nèi)粘i_(kāi)發(fā)過(guò)程中,通常會(huì)涉及到數(shù)據(jù)權(quán)限問(wèn)題,下面以我們常見(jiàn)的一種場(chǎng)景舉例:
一個(gè)公司有很多部門(mén),每個(gè)人所處的部門(mén)和角色也不同,所以數(shù)據(jù)權(quán)限也可能不同,比如超級(jí)管理員可以查看某張表的素有信息,部門(mén)領(lǐng)導(dǎo)可以查看該部門(mén)下的相關(guān)信息,部門(mén)普通人員只可以查看個(gè)人相關(guān)信息,而且由于角色的不同,各個(gè)角色所能查看到的數(shù)據(jù)庫(kù)字段也可能不相同,那么此處就涉及到了數(shù)據(jù)權(quán)限相關(guān)的問(wèn)題。
那么我們?cè)撊绾翁幚頂?shù)據(jù)權(quán)限相關(guān)的問(wèn)題呢?
我們提供一種通過(guò)Mybatis攔截器實(shí)現(xiàn)的方式,下面我們來(lái)具體實(shí)現(xiàn)一下
具體實(shí)現(xiàn)
pom.xml依賴(lài)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.2.0</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>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:
# 驗(yàn)證連接是否有效。此參數(shù)必須設(shè)置為非空字符串,下面三項(xiàng)設(shè)置成true才能生效
validation-query: SELECT 1
# 連接是否被空閑連接回收器(如果有)進(jìn)行檢驗(yàn). 如果檢測(cè)失敗, 則連接將被從池中去除
test-while-idle: true
# 是否在從池中取出連接前進(jìn)行檢驗(yàn), 如果檢驗(yàn)失敗, 則從池中去除連接并嘗試取出另一個(gè)
test-on-borrow: true
# 是否在歸還到池中前進(jìn)行檢驗(yàn)
test-on-return: false
# 連接在池中最小生存的時(shí)間,單位是毫秒
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代碼實(shí)現(xiàn)
DataScode.java
@Data
public class DataScope {
// sql過(guò)濾條件
String sqlCondition;
// 需要過(guò)濾的結(jié)果字段
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("執(zhí)行intercept方法:{}", invocation.toString());
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameterObject = args[1];
// 查找參數(shù)中包含DataScope類(lèi)型的參數(shù)
DataScope dataScope = findDataScopeObject(parameterObject);
if (dataScope == null) {
return invocation.proceed();
}
if (!ObjectUtils.isEmpty(dataScope.getSqlCondition())) {
// id為執(zhí)行的mapper方法的全路徑名,如com.mapper.UserMapper
String id = ms.getId();
// sql語(yǔ)句類(lèi)型 select、delete、insert、update
String sqlCommandType = ms.getSqlCommandType().toString();
// 僅攔截 select 查詢(xún)
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一個(gè)查詢(xún)語(yǔ)句對(duì)象
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
// 把新的查詢(xún)放到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("改寫(xiě)的SQL: {}", newSql);
}
Object result = invocation.proceed();
return handleReslut(result, Arrays.asList(dataScope.getFilterFields()));
}
/**
* 定義一個(gè)內(nèi)部輔助類(lèi),作用是包裝 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(Properties properties) {
// 獲取屬性
// String value1 = properties.getProperty("prop1");
log.info("properties方法:{}", properties.toString());
}
/**
* 查找參數(shù)是否包括DataScope對(duì)象
*
* @param parameterObj 參數(shù)列表
* @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<String> filterFields){
if(returnValue != null && !ObjectUtils.isEmpty(filterFields)){
if (returnValue instanceof ArrayList<?>){
List<?> list = (ArrayList<?>) returnValue;
List<Object> newList = new ArrayList<Object>();
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> T decrypt(List<String> 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
<mapper namespace="com.mk.mapper.SalariesMapper">
<select id="pageList" resultType="com.mk.entity.Salaries">
SELECT * from salaries where salary between #{start} and #{end}
</select>
<select id="getByEmpNo" resultType="java.util.Map">
select * from salaries where emp_no = #{empNo} limit 0,1
</select>
</mapper>SalariesMapper.java
@Mapper
public interface SalariesMapper extends BaseMapper<Salaries> {
List<Salaries> pageList(DataScope dataScope, @Param("start") int start, @Param("end") int end, Page<Salaries> page);
Map<String, Object> getByEmpNo(DataScope dataScope, @Param("empNo") int empNo);
}SalariesService.java
@Service
public class SalariesService extends ServiceImpl<SalariesMapper, Salaries> {
@Autowired
private SalariesMapper salariesMapper;
public List<Salaries> getList(){
Page<Salaries> page = new Page<>(1, 10);
DataScope dataScope = new DataScope();
// 設(shè)置查詢(xún)條件
dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'");
// 將結(jié)果集過(guò)濾掉salary和toDate字段
dataScope.setFilterFields(new String[]{"salary", "toDate"});
return salariesMapper.pageList(dataScope, 60000, 70000, page);
}
public Map<String, Object> getByEmpNo() {
DataScope dataScope = new DataScope();
// 將結(jié)果集過(guò)濾掉salary和toDate字段
dataScope.setFilterFields(new String[]{"salary", "toDate"});
return salariesMapper.getByEmpNo(dataScope, 10001);
}
}啟動(dòng)服務(wù),執(zhí)行相關(guān)操作,sql在執(zhí)行之前會(huì)執(zhí)行DataScopeInterceptor攔截器中的邏輯,從而改變sql,具體的相關(guān)操作就是將原來(lái)的sql語(yǔ)句origSql在外層包裝一層過(guò)濾條件,如:select * from (origSql) 過(guò)濾條件,此處的過(guò)濾條件要封裝到DataScope對(duì)象中
例如:
dataScope.setSqlCondition("s where 1=1 and s.emp_no = '10001'")
那么在經(jīng)過(guò)攔截器處理以后要執(zhí)行的sql語(yǔ)句為
select * from (origSql) s where 1=1 and s.emp_no = '10001'
從而實(shí)現(xiàn)數(shù)據(jù)權(quán)限相操作,當(dāng)然此處的過(guò)濾條件只是為了演示效果舉的一個(gè)例子
而已,在實(shí)際開(kāi)發(fā)過(guò)程中要根據(jù)用戶(hù)角色等等設(shè)置具體的過(guò)濾條件。
到此這篇關(guān)于Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限詳解的文章就介紹到這了,更多相關(guān)Mybatis攔截器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)權(quán)限隔離方式
- MyBatis-Plus數(shù)據(jù)權(quán)限插件的簡(jiǎn)單使用
- MybatisPlus實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離的示例詳解
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的方法
- Mybatis-plus通過(guò)添加攔截器實(shí)現(xiàn)簡(jiǎn)單數(shù)據(jù)權(quán)限
- mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限項(xiàng)目實(shí)踐
- mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)代碼
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的示例
- Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼
- Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能
相關(guān)文章
java使用XSSFWorkbook實(shí)現(xiàn)讀寫(xiě)Excel
這篇文章主要為大家詳細(xì)介紹了java如何通過(guò)使用XSSFWorkbook實(shí)現(xiàn)讀寫(xiě)Excel功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04
springboot?maven?打包插件介紹及注意事項(xiàng)說(shuō)明
這篇文章主要介紹了springboot?maven?打包插件介紹及注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java線(xiàn)程之守護(hù)線(xiàn)程(Daemon)用法實(shí)例
這篇文章主要介紹了Java線(xiàn)程之守護(hù)線(xiàn)程(Daemon)用法,較為詳細(xì)的分析了守護(hù)線(xiàn)程的功能與實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
Java中EnumMap和EnumSet枚舉操作類(lèi)的簡(jiǎn)單使用詳解
這篇文章主要介紹了Java中EnumMap和EnumSet枚舉操作類(lèi)的簡(jiǎn)單使用詳解,EnumMap是Map接口的一種實(shí)現(xiàn),專(zhuān)門(mén)用于枚舉類(lèi)型的鍵,所有枚舉的鍵必須來(lái)自同一個(gè)枚舉?EnumMap不允許鍵為空,允許值為空,需要的朋友可以參考下2023-11-11
Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼
Shiro是Apache?的一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼學(xué)和會(huì)話(huà)管理,Shiro?主要分為兩個(gè)部分就是認(rèn)證和授權(quán)兩部分,這篇文章主要介紹了Springboot+Shiro+Mybatis+mysql實(shí)現(xiàn)權(quán)限安全認(rèn)證的示例代碼,需要的朋友可以參考下2024-07-07
SpringBoot整合jasypt實(shí)現(xiàn)數(shù)據(jù)加密的步驟
聽(tīng)說(shuō)過(guò)jasypt嗎?它可是一個(gè)超級(jí)流行的Java庫(kù)哦,提供了簡(jiǎn)單又高效的加密和解密接口,整合jasypt后,我們的SpringBoot應(yīng)用就能輕松處理敏感數(shù)據(jù)的加密和解密,而不必為復(fù)雜的加密算法頭疼啦,下面給大家介紹SpringBoot整合jasypt實(shí)現(xiàn)數(shù)據(jù)加密的步驟,感興趣的朋友一起看看吧2025-04-04

