feat(data/mybatis-plus): 新增数据权限默认解决方案

1.新增数据权限默认解决方案
2.调整 BaseMapper 所属模块 crud => mybatis-plus
3.优化 mybatis-plus 模块包结构
This commit is contained in:
2023-12-21 23:20:03 +08:00
parent 2a70a9a252
commit 621a5e3b22
13 changed files with 475 additions and 17 deletions

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.*;
/**
* 是否启用数据权限注解
*
* @author Charles7c
* @since 1.1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = "enabled", havingValue = "true")
public @interface ConditionalOnEnabledDataPermission {}

View File

@@ -17,7 +17,6 @@
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -46,15 +45,28 @@ public class MyBatisPlusExtensionProperties {
private String mapperPackage;
/**
* 数据权限处理器实现类
* 数据权限插件配置
*/
private Class<? extends DataPermissionHandler> dataPermissionHandlerImpl;
private DataPermissionProperties dataPermission;
/**
* 分页插件配置
*/
private PaginationProperties pagination;
/**
* 数据权限插件配置属性
*/
@Data
public static class DataPermissionProperties {
/**
* 是否启用数据权限插件
*/
private boolean enabled = false;
}
/**
* 分页插件配置属性
*/

View File

@@ -17,7 +17,7 @@
package top.charles7c.continew.starter.data.mybatis.plus.autoconfigure;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -36,6 +36,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import top.charles7c.continew.starter.core.handler.GeneralPropertySourceFactory;
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionFilter;
import top.charles7c.continew.starter.data.mybatis.plus.datapermission.DataPermissionHandlerImpl;
/**
* MyBatis Plus 自动配置
@@ -60,13 +62,13 @@ public class MybatisPlusAutoConfiguration {
public MybatisPlusInterceptor mybatisPlusInterceptor(MyBatisPlusExtensionProperties properties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
Class<? extends DataPermissionHandler> dataPermissionHandlerImpl = properties.getDataPermissionHandlerImpl();
if (null != dataPermissionHandlerImpl) {
interceptor.addInnerInterceptor(new DataPermissionInterceptor(ReflectUtil.newInstance(dataPermissionHandlerImpl)));
MyBatisPlusExtensionProperties.DataPermissionProperties dataPermissionProperties = properties.getDataPermission();
if (null != dataPermissionProperties && dataPermissionProperties.isEnabled()) {
interceptor.addInnerInterceptor(new DataPermissionInterceptor(SpringUtil.getBean(DataPermissionHandler.class)));
}
// 分页插件
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
if (properties.isEnabled() && paginationProperties.isEnabled()) {
if (null != paginationProperties && paginationProperties.isEnabled()) {
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
}
// 防全表更新与删除插件
@@ -74,6 +76,16 @@ public class MybatisPlusAutoConfiguration {
return interceptor;
}
/**
* 数据权限处理器
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledDataPermission
public DataPermissionHandler dataPermissionHandler(DataPermissionFilter dataPermissionFilter) {
return new DataPermissionHandlerImpl(dataPermissionFilter);
}
/**
* ID 生成器配置仅在主键类型idType配置为 ASSIGN_ID 或 ASSIGN_UUID 时有效)
* <p>

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.base;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import java.util.Collection;
/**
* Mapper 基类
*
* @param <T> 实体类
* @author Charles7c
* @since 1.0.0
*/
public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> {
/**
* 批量插入记录
*
* @param entityList 实体列表
* @return 是否成功
*/
default boolean insertBatch(Collection<T> entityList) {
return Db.saveBatch(entityList);
}
/**
* 批量更新记录
*
* @param entityList 实体列表
* @return 是否成功
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
}
/**
* 链式查询
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return ChainWrappers.queryChain(this);
}
/**
* 链式查询lambda 式)
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(this, this.currentEntityClass());
}
/**
* 链式查询lambda 式)
*
* @param entity 实体对象
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery(T entity) {
return ChainWrappers.lambdaQueryChain(this, entity);
}
/**
* 链式更改
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return ChainWrappers.updateChain(this);
}
/**
* 链式更改lambda 式)
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(this);
}
/**
* 获取实体类 Class 对象
*
* @return 实体类 Class 对象
*/
default Class<T> currentEntityClass() {
return (Class<T>) ClassUtil.getTypeArgument(this.getClass(), 0);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* 数据权限注解
*
* @author Charles7c
* @since 1.1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
/**
* Alias for the {@link #tableAlias()} attribute.
*/
@AliasFor("tableAlias")
String value() default "";
/**
* 表别名
*/
@AliasFor("value")
String tableAlias() default "";
/**
* ID
*/
String id() default "id";
/**
* 部门 ID
*/
String deptId() default "dept_id";
/**
* 用户 ID
*/
String userId() default "create_user";
/**
* 角色 ID角色和部门关联表
*/
String roleId() default "role_id";
/**
* 部门表别名
*/
String deptTableAlias() default "sys_dept";
/**
* 角色和部门关联表别名
*/
String roleDeptTableAlias() default "sys_role_dept";
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
/**
* 当前用户信息
*
* @author Charles7c
* @since 1.1.0
*/
@Data
public class DataPermissionCurrentUser {
/**
* 用户 ID
*/
private String userId;
/**
* 角色列表
*/
private Set<CurrentUserRole> roles;
/**
* 部门 ID
*/
private String deptId;
/**
* 当前用户角色信息
*/
@Data
@AllArgsConstructor
public static class CurrentUserRole {
/**
* 角色 ID
*/
private String roleId;
/**
* 数据权限
*/
private DataScope dataScope;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
/**
* 数据权限过滤器接口
*
* @author Charles7c
* @since 1.1.0
*/
public interface DataPermissionFilter {
/**
* 是否过滤
*
* @return true过滤false不过滤
*/
boolean isFilter();
/**
* 获取当前用户信息
*
* @return 当前用户信息
*/
DataPermissionCurrentUser getCurrentUser();
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import top.charles7c.continew.starter.core.constant.StringConstants;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;
/**
* 数据权限处理器实现类
*
* @author <a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
* @author Charles7c
* @since 1.1.0
*/
@Slf4j
@RequiredArgsConstructor
public class DataPermissionHandlerImpl implements DataPermissionHandler {
private final DataPermissionFilter dataPermissionFilter;
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
Class<?> clazz =
Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringConstants.DOT)));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringConstants.DOT) + 1);
Method[] methodArr = clazz.getMethods();
for (Method method : methodArr) {
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
if (null != dataPermission
&& (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
if (dataPermissionFilter.isFilter()) {
return buildDataScopeFilter(dataPermission, where);
}
}
}
} catch (ClassNotFoundException e) {
log.error("Data permission handler build data scope filter occurred an error: {}.", e.getMessage(), e);
}
return where;
}
/**
* 构建数据范围过滤条件
*
* @param dataPermission
* 数据权限
* @param where
* 当前查询条件
* @return 构建后查询条件
*/
private Expression buildDataScopeFilter(DataPermission dataPermission, Expression where) {
Expression expression = null;
String tableAlias = dataPermission.tableAlias();
String id = dataPermission.id();
String deptId = dataPermission.deptId();
DataPermissionCurrentUser currentUser = dataPermissionFilter.getCurrentUser();
Set<DataPermissionCurrentUser.CurrentUserRole> roles = currentUser.getRoles();
for (DataPermissionCurrentUser.CurrentUserRole role : roles) {
DataScope dataScope = role.getDataScope();
if (DataScope.ALL.equals(dataScope)) {
return where;
}
if (DataScope.DEPT_AND_CHILD.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` in (select `id` from `sys_dept` where `id` = xxx or
// find_in_set(xxx, `ancestors`));
// 构建子查询
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(id))));
select.setFromItem(new Table(dataPermission.deptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(id));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(currentUser.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelectBody(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(tableAlias, deptId));
inExpression.setRightExpression(subSelect);
expression = null != expression ? new OrExpression(expression, inExpression) : inExpression;
} else if (DataScope.DEPT.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` = xxx;
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(tableAlias, deptId));
equalsTo.setRightExpression(new LongValue(currentUser.getDeptId()));
expression = null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
} else if (DataScope.SELF.equals(dataScope)) {
// select t1.* from table as t1 where t1.`create_user` = xxx;
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(this.buildColumn(tableAlias, dataPermission.userId()));
equalsTo.setRightExpression(new LongValue(currentUser.getUserId()));
expression = null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
} else if (DataScope.CUSTOM.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` in (select `dept_id` from `sys_role_dept` where
// `role_id` = xxx);
// 构建子查询
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(deptId))));
select.setFromItem(new Table(dataPermission.roleDeptTableAlias()));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataPermission.roleId()));
equalsTo.setRightExpression(new LongValue(role.getRoleId()));
select.setWhere(equalsTo);
subSelect.setSelectBody(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(this.buildColumn(tableAlias, deptId));
inExpression.setRightExpression(subSelect);
expression = null != expression ? new OrExpression(expression, inExpression) : inExpression;
}
}
return null != where ? new AndExpression(where, new Parenthesis(expression)) : expression;
}
/**
* 构建 Column
*
* @param tableAlias
* 表别名
* @param columnName
* 字段名称
* @return 带表别名字段
*/
private Column buildColumn(String tableAlias, String columnName) {
if (StringUtils.isNotEmpty(tableAlias)) {
columnName = String.format("%s.%s", tableAlias, columnName);
}
return new Column(columnName);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.datapermission;
/**
* 数据权限枚举
*
* @author Charles7c
* @since 1.1.0
*/
public enum DataScope {
/**
* 全部数据权限
*/
ALL,
/**
* 本部门及以下数据权限
*/
DEPT_AND_CHILD,
/**
* 本部门数据权限
*/
DEPT,
/**
* 仅本人数据权限
*/
SELF,
/**
* 自定义数据权限
*/
CUSTOM,
}

View File

@@ -14,9 +14,7 @@
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.annotation;
import top.charles7c.continew.starter.data.mybatis.plus.enums.QueryType;
package top.charles7c.continew.starter.data.mybatis.plus.query;
import java.lang.annotation.*;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.util;
package top.charles7c.continew.starter.data.mybatis.plus.query;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -26,8 +26,6 @@ import lombok.extern.slf4j.Slf4j;
import top.charles7c.continew.starter.core.exception.BadRequestException;
import top.charles7c.continew.starter.core.util.ReflectUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import top.charles7c.continew.starter.data.mybatis.plus.annotation.Query;
import top.charles7c.continew.starter.data.mybatis.plus.enums.QueryType;
import java.lang.reflect.Field;
import java.util.ArrayList;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.enums;
package top.charles7c.continew.starter.data.mybatis.plus.query;
import lombok.Getter;
import lombok.RequiredArgsConstructor;