1. 问题:项目要求数据权限配置查询,全部、自定义、部门、部门及下级部门、个人。要求做统一处理。
  2. 分析:
  3. 数据权限精确到个人。那么每张表里面都要有创建人字段。每次插入数据都要保存创建人。查询的时候才能区分。
  4. mybatis-plus统一处理创建时间,创建人,更新时间,更新人统一处理创建人
  5. 查询的时候每个查询加条件代码太复杂。所以sql加统一处理例:
  6. SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)
  7. 第一个%s:要查询的字段
  8. 第二个%s:sql语句例:select * from user
  9. 第三个%s:条件字段,从 第二个%s里面挑选
  10. 第四个%s:查询的值 

具体实现:

创建一个公共的entity实体类,所有的实体类继承这个类,当然你的表里面要有相应的字段

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;


/**
 * 父级实体-公共属性
 * @param
 */
@Data
@Accessors(chain = true)
public class OkayxBaseEntity<T extends OkayxBaseEntity<?>> extends Model{

	/**
	 * 更新者
	 */
	@ApiModelProperty(value="更新者")
	@TableField(fill= FieldFill.INSERT_UPDATE)
	private Integer updateBy;

	/**
	 * 创建者
	 */
	@ApiModelProperty(value="创建者")
	@TableField(fill= FieldFill.INSERT)
	private Integer createBy;

	/**
	 * 创建时间
	 */
	@ApiModelProperty(value="创建时间")
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
	@TableField(fill= FieldFill.INSERT)
	private LocalDateTime createTime;

	/**
	 * 更新时间
	 */
	@ApiModelProperty(value="更新时间")
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
	@TableField(fill= FieldFill.INSERT_UPDATE)
	private LocalDateTime updateTime;

	/**
	 * 是否删除
	 */
	@ApiModelProperty(value="delFlag")
	private String delFlag;

}

数据权限等级枚举类,枚举类一般对应权限管理,一起存在权限表。例:

 

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 数据权限类型
 */
@Getter
@AllArgsConstructor
public enum DataScopeTypeEnum {

	/**
	 * 查询全部数据
	 */
	ALL(0, "全部"),

	/**
	 * 自定义
	 */
	CUSTOM(1, "自定义"),

	/**
	 * 本级及子级
	 */
	OWN_CHILD_LEVEL(2, "本级及子级"),

	/**
	 * 本级
	 */
	OWN_LEVEL(3, "本级"),

	/**
	 * 个人(自己)
	 */
	OWN(4,"个人");


	/**
	 * 类型
	 */
	private final int type;

	/**
	 * 描述
	 */
	private final String description;

}

数据权限的函数类型,(查询全部字段,查询条数),看上面的分析 ,也就是第一个%s

/**
 * 数据权限函数类型
 */
@Getter
@AllArgsConstructor
public enum DataScopeFuncEnum {

	/**
	 * 查询全部数据 SELECT * FROM (originSql) temp_data_scope WHERE temp_data_scope.dept_id IN
	 * (1)
	 */
	ALL("*", "全部"),

	/**
	 * 查询函数COUNT SELECT COUNT(1) FROM (originSql) temp_data_scope WHERE
	 * temp_data_scope.dept_id IN (1)
	 */
	COUNT("COUNT(1)", "自定义");

	/**
	 * 类型
	 */
	private final String type;

	/**
	 * 描述
	 */
	private final String description;

}

数据字段查询参数:

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class DataScope extends HashMap {

	/**
	 * 限制范围的字段名称
	 */
	// private String scopeName = "dept_id";

	/**
	 * 限制范围的字段名称-到个人
	 */
	private String scopeName = "create_by";

	/**
	 * 具体的数据范围-部门
	 */
	private List<Integer> deptIds = new ArrayList<>();

	/**
	 * 具体的数据范围-创建人
	 */
	private List<Integer> createBys = new ArrayList<>();

	/**
	 * 是否只查询本部门
	 */
	private Boolean isOnly = false;

	/**
	 * 函数名称,默认 SELECT * ;
	 *
	 * <ul>
	 * <li>COUNT(1)</li>
	 * </ul>
	 */
	private DataScopeFuncEnum func = DataScopeFuncEnum.ALL;

}
判断处理器,抽象服务扩展。实现类处理用户权限
import java.util.List;

/**
 * data scope 判断处理器,抽象服务扩展
 */
public interface DataScopeHandle {

	/**
	 * 计算用户数据权限
	 * @param userList 用户ID,如果为空表示没有任何数据权限。
	 * @return 返回true表示无需进行数据过滤处理,返回false表示需要进行数据过滤
	 */
	Boolean calcScope(List<Integer> userList );

}

判断处理器实现类。处理用户id。(选择部门返回本部门所有人的id,选择个人返回个人id......)

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.okay.okayx.admin.api.entity.SysDeptRelation;
import com.okay.okayx.admin.api.entity.SysRole;
import com.okay.okayx.admin.api.entity.SysUser;
import com.okay.okayx.admin.api.feign.RemoteDataScopeService;
import com.okay.okayx.common.core.constant.SecurityConstants;
import com.okay.okayx.common.security.service.OkayxUser;
import com.okay.okayx.common.security.util.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author xuxiang
 * @date 2021/0714
 */
public class OkayxUserDatascopeHandle  implements DataScopeHandle {
	@Autowired
	private RemoteDataScopeService dataScopeService;

	/**
	 * 计算用户数据权限
	 * @param userIdList
	 * @return
	 */
	@Override
	public Boolean calcScope(List<Integer> userIdList) {
		OkayxUser user = SecurityUtils.getUser();
		List<Integer> deptIds = new ArrayList<>();
		List<String> roleIdList = user.getAuthorities().stream().map(GrantedAuthority::getAuthority)
				.filter(authority -> authority.startsWith(SecurityConstants.ROLE))
				.map(authority -> authority.split(StrUtil.UNDERLINE)[1]).collect(Collectors.toList());
		// 当前用户的角色为空
		if (CollectionUtil.isEmpty(roleIdList)) {
			return false;
		}
		SysRole role = dataScopeService.getRoleList(roleIdList).getData().stream()
				.min(Comparator.comparingInt(SysRole::getDsType)).orElse(null);
		// 角色有可能已经删除了
		if (role == null) {
			return false;
		}
		Integer dsType = role.getDsType();
		// 查询全部
		if (DataScopeTypeEnum.ALL.getType() == dsType) {
			return true;
		}
		// 自定义
		if (DataScopeTypeEnum.CUSTOM.getType() == dsType && StrUtil.isNotBlank(role.getDsScope())) {
			String dsScope = role.getDsScope();
			deptIds.addAll(
					Arrays.stream(dsScope.split(StrUtil.COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
			userIdList = dataScopeService.getUserList(deptIds).getData().stream().map(SysUser::getUserId).collect(Collectors.toList());
		}
		// 查询本级及其下级
		if (DataScopeTypeEnum.OWN_CHILD_LEVEL.getType() == dsType) {
			deptIds = dataScopeService.getDescendantList(user.getDeptId()).getData().stream()
					.map(SysDeptRelation::getDescendant).collect(Collectors.toList());
			userIdList = dataScopeService.getUserList(deptIds).getData().stream().map(SysUser::getUserId).collect(Collectors.toList());
		}
		// 只查询本级
		if (DataScopeTypeEnum.OWN_LEVEL.getType() == dsType) {
			Integer deptId = user.getDeptId();
			deptIds.add(deptId);
			userIdList = dataScopeService.getUserList(deptIds).getData().stream().map(SysUser::getUserId).collect(Collectors.toList());
		}
		// 只查询本级
		if (DataScopeTypeEnum.OWN.getType() == dsType) {
			userIdList.add(user.getId());
		}

		return false;
	}
}
数据权限拦截,sql执行前拦截

// 查找参数中包含DataScope类型的参数
   DataScope dataScope = findDataScopeObject(parameterObject);

看这段代码也就是参数里面含有DataScope的都会做数据过虑

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.Setter;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Map;

/**
 * 数据权限拦截
 * @author xuxaing
 * @date 2021/07/14
 */
public class DataScopeInnerInterceptor1 implements InnerInterceptor {

	@Setter
	private DataScopeHandle dataScopeHandle;

	@Override
	public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
							ResultHandler resultHandler, BoundSql boundSql) {
		PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);

		String originalSql = boundSql.getSql();
		Object parameterObject = boundSql.getParameterObject();

		// 查找参数中包含DataScope类型的参数
		DataScope dataScope = findDataScopeObject(parameterObject);
		if (dataScope == null) {
			return;
		}

		String scopeName = dataScope.getScopeName();
		List<Integer> createBys = dataScope.getCreateBys();
		// 优先获取赋值数据
		if (CollUtil.isEmpty(createBys) && dataScopeHandle.calcScope(createBys)) {
			originalSql = String.format("SELECT %s FROM (%s) temp_data_scope", dataScope.getFunc().getType(),
					originalSql);
			mpBs.sql(originalSql);
			return;
		}

		if (createBys.isEmpty()) {
			originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
					dataScope.getFunc().getType(), originalSql);
		}
		else {
			String join = CollectionUtil.join(createBys, ",");
			originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
					dataScope.getFunc().getType(), originalSql, scopeName, join);
		}

		mpBs.sql(originalSql);
	}

	/**
	 * 查找参数是否包括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;
	}
}

mybatis配置类:

数据拦截器DataScopeInnerInterceptor1
判断处理器OkayxUserDatascopeHandle 

配置到配置类

import java.util.List;

import javax.sql.DataSource;

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.okay.okayx.common.data.datascope.*;
import com.okay.okayx.common.data.resolver.SqlFilterArgumentResolver;
import com.okay.okayx.common.data.tenant.OkayxTenantHandler;
import com.okay.okayx.common.security.service.OkayxUser;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 */
@Configuration
@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusConfiguration implements WebMvcConfigurer {

	/**
	 * 增加请求参数解析器,对请求中的参数注入SQL 检查
	 * @param resolverList
	 */
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolverList) {
		resolverList.add(new SqlFilterArgumentResolver());
	}

	/**
	 * okayx 默认数据权限处理器
	 * @return OkayxDefaultDatascopeHandle
	 */
	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnClass(OkayxUser.class)
	public DataScopeHandle dataScopeHandle() {
		return new OkayxUserDatascopeHandle();
	}

	/**
	 * mybatis plus 拦截器配置
	 * @return OkayxDefaultDatascopeHandle
	 */
	@Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		// 数据权限-新
		DataScopeInnerInterceptor1 dataScopeInnerInterceptor = new DataScopeInnerInterceptor1();
		dataScopeInnerInterceptor.setDataScopeHandle(dataScopeHandle());
		interceptor.addInnerInterceptor(dataScopeInnerInterceptor);
		// 分页支持
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
		return interceptor;
	}



	/**
	 * 扩展 mybatis-plus baseMapper 支持数据权限
	 * @return
	 */
	@Bean
	@ConditionalOnBean(DataScopeHandle.class)
	public DataScopeSqlInjector dataScopeSqlInjector() {
		return new DataScopeSqlInjector();
	}

}

前面过虑的事情都做写了,然后我们来处理业务。

写一个公共Mapper,所有的Mapper都实现这个Mapper

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 扩展通用 Mapper,支持数据权限 和批量插入
 *
 * @author okayx
 * @date 2020-06-17
 */
public interface OkayxBaseMapper<T> extends BaseMapper<T> {

	/**
	 * 根据 entity 条件,查询全部记录
	 * @param queryWrapper 实体对象封装操作类(可以为 null)
	 * @param scope 数据权限范围
	 * @return List<T>
	 */
	List<T> selectListByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);

	/**
	 * 根据 entity 条件,查询全部记录(并翻页)
	 * @param page 分页查询条件(可以为 RowBounds.DEFAULT)
	 * @param queryWrapper 实体对象封装操作类(可以为 null)
	 * @param scope 数据权限范围
	 * @return Page
	 */
	<E extends IPage<T>> E selectPageByScope(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper,
			DataScope scope);

	/**
	 * 根据 Wrapper 条件,查询总记录数
	 * @param queryWrapper 实体对象封装操作类(可以为 null)
	 * @param scope 数据权限范围
	 * @return Integer
	 */
	Integer selectCountByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);

	/**
	 * 批量插入 仅适用于 mysql
	 * @param entityList 实体列表
	 * @return 影响行数
	 */
	Integer insertBatchSomeColumn(List<T> entityList);

}

service层代码:这里只展示一个方法。跟分页查询相似,只多了一个DataScope的默认对象

@Override
	public IPage<CloudManagerSjml> getSjmlRoles(Page page, QueryWrapper<CloudManagerSjml> queryWrapper) {
		return cloudManagerSjmlMapper.selectPageByScope(page,queryWrapper,new DataScope());
	}

重启项目,成功

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐