mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)代碼
Mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)
說(shuō)明
數(shù)據(jù)權(quán)限是平臺(tái)系統(tǒng)中不可分割的一部分,在mybatis框架中,大部分都是基于mybatis攔截器進(jìn)行數(shù)據(jù)權(quán)限的插入,有的將數(shù)據(jù)權(quán)限參數(shù)作為XML的標(biāo)簽,有的是基于注解方式,但是不管這兩種方式如何,都必須在攔截器中處理自己解析SQL,稍有不慎或者說(shuō)沒解析到就會(huì)出現(xiàn)各種奇奇怪怪的問題。在引入mybatis-plus以后通過查看myabtis-mate插件的部分示例。結(jié)合了mybatis-plus的插件方式,做出了自己的注解方式的數(shù)據(jù)權(quán)限,雖然可能存在一部分的局限性,但很好的解決了我們自己去解析SQL的功能。
自定義注解部分
建立兩個(gè)注解與一個(gè)枚舉,枚舉是實(shí)現(xiàn)插入數(shù)據(jù)權(quán)限SQL的前置
注解:@DataScope,@DataColumn.這個(gè)兩個(gè)注解作為在Mapper方法上使用
// @DataScope
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataScope {
DataColumn[] value() default {};
}//@DataColumn
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Repeatable(DataScope.class)
public @interface DataColumn {
String alias() default "";
String name();
ColumnType type() default ColumnType.USER;
}自定義枚舉
自定義一個(gè)枚舉:ColumnType 枚舉中有個(gè)Class 是屬于繼承AbstractDataColumnStrategy(抽象數(shù)據(jù)列處理策略)
public enum ColumnType {
USER(UserDataColumnStrategy.class),
DEPT(DeptDataColumnStrategy.class);
private Class<? extends AbstractDataColumnStrategy> clazz;
ColumnType(Class<? extends AbstractDataColumnStrategy> userDataColumnStrategyClass) {
}
public Class<? extends AbstractDataColumnStrategy> getClazz() {
return clazz;
}
}自定義Mybatis-Plus的插件
mybatis-plus的自定義插件需要繼承JsqlParserSupport,且實(shí)現(xiàn)InnerInterceptor接口。其中JsqlParserSupport是Myabtis-Plus使用JsqlParser依賴改造的通過JsqlParser來(lái)解析需要執(zhí)行的SQL。
public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
private static final String COUNT_PRE = "_COUNT";
private MappedStatement ms;
private DataScope dataScope;
//重寫查詢之前方法
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
this.ms = ms;
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
return;
}
//獲取權(quán)限注解 如果沒有加注解 則忽略數(shù)據(jù)權(quán)限
DataScope dataScope = this.getPermissionAnnotation(ms);
if (dataScope == null) {
return;
}
this.dataScope = dataScope;
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
}
//重寫處理Select方法
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
SelectBody selectBody = select.getSelectBody();
//如果是_COUNT結(jié)尾的SQL 則是PageHelper的統(tǒng)計(jì)SQL 包裹了原SQL 則需要獲取到子句 進(jìn)行條件添加
try {
if (obj.toString().endsWith(COUNT_PRE)) {
PlainSelect plainSelect = (PlainSelect) selectBody;
FromItem fromItem = plainSelect.getFromItem();
if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
SelectBody selectBody1 = subSelect.getSelectBody();
this.setWhere((PlainSelect) selectBody1, (String) obj);
}
} else {
this.setWhere((PlainSelect) selectBody, (String) obj);
}
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
logger.error("處理數(shù)據(jù)權(quán)限SQL異常 error:{}", e);
throw new RuntimeException(e.getMessage());
}
}
//設(shè)置條件的方法
private void setWhere(PlainSelect plainSelect, String whereSegment) throws IllegalAccessException, InstantiationException {
DataColumn[] columns = this.getDataScope().value();
//如果沒有添加數(shù)據(jù)權(quán)限列 返回
if (columns.length < 1) {
return;
}
//獲取所有需要處理的數(shù)據(jù)權(quán)限列,根據(jù)枚舉獲取SQL處理的策略
for (DataColumn column : columns) {
Class<? extends AbstractDataColumnStrategy> clazz = column.type().getClazz();
AbstractDataColumnStrategy strategy = clazz.newInstance();
strategy.setAlias(column.alias());
strategy.setName(column.name());
strategy.setPlainSelect(plainSelect);
PlainSelect strategyPlainSelect = strategy.getPlainSelect();
plainSelect = strategyPlainSelect;
}
}
/**
* 獲得權(quán)限注釋
*
* @param ms
* @return {@link DataScope}
*/
private DataScope getPermissionAnnotation(MappedStatement ms) {
DataScope data = null;
try {
// id為執(zhí)行的mapper方法的全路徑名,如com.mapper.UserMapper
String id = ms.getId();
log.info("解析得到全類型名稱 ID:{}", id);
//統(tǒng)計(jì)SQL取得注解也是實(shí)際查詢id上得注解,所以需要去掉_COUNT
if (id.contains(COUNT_PRE)) {
id = id.replace(COUNT_PRE, "");
}
//獲取類名和方法名
String className = id.substring(0, id.lastIndexOf("."));
String methodName = id.substring(id.lastIndexOf(".") + 1);
final Class<?> cls = Class.forName(className);
final Method[] method = cls.getMethods();
//反射 獲取注解
for (Method me : method) {
if (me.getName().equals(methodName) && me.isAnnotationPresent(DataScope.class)) {
data = me.getAnnotation(DataScope.class);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return data;
}
}本地線程
通過本地線程或者阿里的父子傳遞的線程將數(shù)據(jù)權(quán)限解析后的用戶或部門進(jìn)行傳遞
@Data
public class DataPermissionDto {
private List<Long> userIds;
private List<Long> deptIds;
private Long myUserId;
private Long myDeptId;
private Long tenantId;
}
//線程池
public class DataPermissionTenantHolder {
/**
* 支持父子線程之間的數(shù)據(jù)傳遞
*/
private static final ThreadLocal<DataPermissionDto> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
public static <T extends DataPermissionDto> void setTenant(T tenant) {
CONTEXT_HOLDER.set(tenant);
}
public static <T extends DataPermissionDto> T getTenant() {
return (T) CONTEXT_HOLDER.get();
}
public static void clear() {
CONTEXT_HOLDER.remove();
}
}
```java
public class UserDataColumnStrategy extends AbstractDataColumnStrategy {
@Override
public PlainSelect handleColumn() {
String alias = getAlias();
String name = getName();
String column = name;
if (StringUtils.isNotBlank(alias)) {
column = alias.trim() + "." + name.trim();
}
PlainSelect plainSelect = getPlainSelect();
DataPermissionDto tenant = DataPermissionTenantHolder.getTenant();
List<Long> userIds = tenant.getUserIds();
//如果線程中沒有 數(shù)據(jù)權(quán)限中的用戶集合
if (CollectionUtils.isEmpty(userIds)) {
Long myUserId = tenant.getMyUserId();
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(column));
equalsTo.setRightExpression(new LongValue(myUserId));
if (null == plainSelect.getWhere()) {
// 不存在 where 條件
plainSelect.setWhere(new Parenthesis(equalsTo));
} else {
// 存在 where 條件 and 處理
plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), equalsTo));
}
return plainSelect;
}
// 有數(shù)據(jù)權(quán)限中的用戶集合
ItemsList itemsList = new ExpressionList(userIds.stream().map(LongValue::new).collect(Collectors.toList()));
InExpression inExpression = new InExpression(new Column(column), itemsList);
if (null == plainSelect.getWhere()) {
// 不存在 where 條件
plainSelect.setWhere(new Parenthesis(inExpression));
} else {
// 存在 where 條件 and 處理
plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), inExpression));
}
return plainSelect;
}
}添加自定義插件
想Myabtis-Plus中添加我們的數(shù)據(jù)權(quán)限插件
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//是否開啟多租戶(這里是多租戶插件部分,下次再提)
if (tenantProperties.isEnabled()) {
TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(new TenantLineHandlerInterceptor(tenantProperties));
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
}
//數(shù)據(jù)權(quán)限的插件
interceptor.addInnerInterceptor(new DataPermissionInterceptor());
return interceptor;
}總結(jié)
Mybatis-Plus 提供了自定義插件的配置,通過自定義插件的方式實(shí)現(xiàn)對(duì)執(zhí)行的SQL操作,比如分頁(yè)插件,多租戶插件等等。通過JsqlParser依賴很好的解析執(zhí)行SQL并對(duì)執(zhí)行的SQL進(jìn)行一系列的操作。
到此這篇關(guān)于mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)內(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攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限詳解
- Mybatis-plus通過添加攔截器實(shí)現(xiàn)簡(jiǎn)單數(shù)據(jù)權(quán)限
- mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限項(xiàng)目實(shí)踐
- 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)文章
IDEA SSM整合Redis項(xiàng)目實(shí)例 附源碼
今天給大家普及IDEA SSM整合Redis項(xiàng)目實(shí)例,包括pom.xml 配置和spring-redis.xml 配置代碼,代碼也很簡(jiǎn)單,通過項(xiàng)目實(shí)際案例能更好的幫助大家理解,需要的朋友可以參考下2021-06-06
springcloud使用profile實(shí)現(xiàn)多環(huán)境配置方式
這篇文章主要介紹了springcloud使用profile實(shí)現(xiàn)多環(huán)境配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java 滑動(dòng)窗口最大值的實(shí)現(xiàn)
這篇文章主要介紹了Java 滑動(dòng)窗口最大值,給定一個(gè)數(shù)組 nums,有一個(gè)大小為 k 的滑動(dòng)窗口從數(shù)組的最左側(cè)移動(dòng)到數(shù)組的最右側(cè)。感興趣的可以了解一下2021-05-05
SpringBoot跨域Access-Control-Allow-Origin實(shí)現(xiàn)解析
這篇文章主要介紹了SpringBoot跨域Access-Control-Allow-Origin實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
基于MybatisPlus插件TenantLineInnerInterceptor實(shí)現(xiàn)多租戶功能
這篇文章主要介紹了基于MybatisPlus插件TenantLineInnerInterceptor實(shí)現(xiàn)多租戶功能,需要的朋友可以參考下2021-11-11

