mybatis-plus數據權限實現代碼
Mybatis-plus數據權限實現
說明
數據權限是平臺系統中不可分割的一部分,在mybatis框架中,大部分都是基于mybatis攔截器進行數據權限的插入,有的將數據權限參數作為XML的標簽,有的是基于注解方式,但是不管這兩種方式如何,都必須在攔截器中處理自己解析SQL,稍有不慎或者說沒解析到就會出現各種奇奇怪怪的問題。在引入mybatis-plus以后通過查看myabtis-mate插件的部分示例。結合了mybatis-plus的插件方式,做出了自己的注解方式的數據權限,雖然可能存在一部分的局限性,但很好的解決了我們自己去解析SQL的功能。
自定義注解部分
建立兩個注解與一個枚舉,枚舉是實現插入數據權限SQL的前置
注解:@DataScope,@DataColumn.這個兩個注解作為在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;
}自定義枚舉
自定義一個枚舉:ColumnType 枚舉中有個Class 是屬于繼承AbstractDataColumnStrategy(抽象數據列處理策略)
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,且實現InnerInterceptor接口。其中JsqlParserSupport是Myabtis-Plus使用JsqlParser依賴改造的通過JsqlParser來解析需要執(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;
}
//獲取權限注解 如果沒有加注解 則忽略數據權限
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結尾的SQL 則是PageHelper的統計SQL 包裹了原SQL 則需要獲取到子句 進行條件添加
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("處理數據權限SQL異常 error:{}", e);
throw new RuntimeException(e.getMessage());
}
}
//設置條件的方法
private void setWhere(PlainSelect plainSelect, String whereSegment) throws IllegalAccessException, InstantiationException {
DataColumn[] columns = this.getDataScope().value();
//如果沒有添加數據權限列 返回
if (columns.length < 1) {
return;
}
//獲取所有需要處理的數據權限列,根據枚舉獲取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;
}
}
/**
* 獲得權限注釋
*
* @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);
//統計SQL取得注解也是實際查詢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;
}
}本地線程
通過本地線程或者阿里的父子傳遞的線程將數據權限解析后的用戶或部門進行傳遞
@Data
public class DataPermissionDto {
private List<Long> userIds;
private List<Long> deptIds;
private Long myUserId;
private Long myDeptId;
private Long tenantId;
}
//線程池
public class DataPermissionTenantHolder {
/**
* 支持父子線程之間的數據傳遞
*/
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();
//如果線程中沒有 數據權限中的用戶集合
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;
}
// 有數據權限中的用戶集合
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中添加我們的數據權限插件
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//是否開啟多租戶(這里是多租戶插件部分,下次再提)
if (tenantProperties.isEnabled()) {
TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(new TenantLineHandlerInterceptor(tenantProperties));
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
}
//數據權限的插件
interceptor.addInnerInterceptor(new DataPermissionInterceptor());
return interceptor;
}總結
Mybatis-Plus 提供了自定義插件的配置,通過自定義插件的方式實現對執(zhí)行的SQL操作,比如分頁插件,多租戶插件等等。通過JsqlParser依賴很好的解析執(zhí)行SQL并對執(zhí)行的SQL進行一系列的操作。
到此這篇關于mybatis-plus數據權限實現的文章就介紹到這了,更多相關mybatis-plus數據權限實現內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MybatisPlus自動填充創(chuàng)建(更新)時間問題
在開發(fā)數據庫相關應用時,手動設置創(chuàng)建和更新時間會導致代碼冗余,MybatisPlus提供了自動填充功能,通過實現MetaObjectHandler接口并重寫insertFill、updateFill方法,可以自動維護創(chuàng)建時間、更新時間等字段,極大簡化了代碼,這不僅提高了開發(fā)效率,也保證了數據的可追溯性2024-09-09
spring-mybatis與原生mybatis使用對比分析
這篇文章主要介紹了spring-mybatis與原生mybatis使用對比分析,需要的朋友可以參考下2017-11-11
Java?事務注解@Transactional回滾(try?catch、嵌套)問題
這篇文章主要介紹了Java?@Transactional回滾(try?catch、嵌套)問題,Spring?事務注解?@Transactional?本來可以保證原子性,如果事務內有報錯的話,整個事務可以保證回滾,但是加上try?catch或者事務嵌套,可能會導致事務回滾失敗2022-08-08

