MP自定义拦截器实现数据权限
角色权限划分
编号 | 名称 | 描述 |
---|---|---|
1 | 管理员 | 全部数据权限 |
2 | 普通角色 | 自定义数据权限 |
3 | 部门权限 | 部门权限 |
4 | 部门及以下数据权限 | 部门及以下数据权限 |
5 | 本人数据 | 本人数据 |
大致思路
1、模仿 PageHelper.startPage
用来声明哪些操作需要做范围限制
2、定义 Mybatis plus
自定义拦截器 Interceptor
,用于每次拦截查询 sql
语句,附带数据范围权限sql
条件
3、因为使用了 PageHelper.startPage
分页插件的使用,先计算总数,怎么在这之前拦截,需要拦截多次
4、考虑到 Mybatis
拦截器能够拦截SQL
执行的整个过程,因为我们可以考虑SQL
执行之前,对SQL
进行重写,从而达到数据行权限的目的。
部分代码实现步骤
权限工具类
public class SecurityUtils {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
protected static final ThreadLocal<String> threadLocal = new ThreadLocal();
/**
* 设置权限标识
*/
public static void startDataScope(){
threadLocal.set(SecurityConstants.DATA_SCOPE);
}
/**
* 获取权限标识
*/
public static String getDataScope(){
return threadLocal.get();
}
/**
* 清除权限标识
*/
public static void cleanDataScope(){
threadLocal.remove();
}
}
权限常量
/**
* 权限相关通用常量
*
*/
public class SecurityConstants {
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
}
自定义MybatisPlus拦截器
/**
* @description 数据权限插件
*/
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
@Slf4j
public class MybatisDataPermissionIntercept implements Interceptor {
CCJSqlParserManager parserManager = new CCJSqlParserManager();
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
//TODO 自己要进行的各种处理
String sql = boundSql.getSql();
log.info("原始SQL: {}", sql);
//判断线程内是否有权限信息
String dataScope = SecurityUtils.getDataScope();
if (SecurityConstants.DATA_SCOPE.equals(dataScope)){
// 增强sql
Select select = (Select) parserManager.parse(new StringReader(sql));
SelectBody selectBody = select.getSelectBody();
if (selectBody instanceof PlainSelect) {
this.setWhere((PlainSelect) selectBody);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOperationList = (SetOperationList) selectBody;
List<SelectBody> selectBodyList = setOperationList.getSelects();
selectBodyList.forEach((s) -> {
this.setWhere((PlainSelect) s);
});
}
String dataPermissionSql = select.toString();
log.info("增强SQL: {}", dataPermissionSql);
BoundSql dataPermissionBoundSql = new BoundSql(ms.getConfiguration(), dataPermissionSql, boundSql.getParameterMappings(), parameter);
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, dataPermissionBoundSql);
}
//注:下面的方法可以根据自己的逻辑调用多次,在分页插件中,count 和 page 各调用了一次
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} finally {
//清除线程中权限参数
SecurityUtils.cleanDataScope();
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
protected void setWhere(PlainSelect plainSelect) {
Expression sqlSegment = this.getSqlSegment(plainSelect.getWhere());
if (null != sqlSegment) {
plainSelect.setWhere(sqlSegment);
}
}
@SneakyThrows
public Expression getSqlSegment(Expression where) {
//用户信息是在gateway中去获取的,获取完之后直接存入到header中,然后再调用过程中传递header
//getLoginUser 是获取用户信息,在header中去获取
JSONObject loginUser = getLoginUser();
if (loginUser == null){
return where;
}
Integer deptId = loginUser.getInteger("deptId");
String userId = loginUser.getString("userId");
JSONArray roles = loginUser.getJSONArray("roles");
StringBuilder sqlString = new StringBuilder();
for (Object role : roles) {
JSONObject roleJson = JSONObject.parseObject(role.toString());
String dataScopeNum = roleJson.getString(SecurityConstants.DATA_SCOPE);
Integer roleId = roleJson.getInteger("roleId");
if (DATA_SCOPE_ALL.equals(dataScopeNum)) {
// 全部数据权限
sqlString = new StringBuilder();
break;
} else if (DATA_SCOPE_CUSTOM.equals(dataScopeNum)) {
sqlString.append(" OR `sys_dept`.dept_id IN ( SELECT dept_id FROM `sys_role_dept` WHERE role_id = '")
.append(roleId)
.append("' ) ");
} else if (DATA_SCOPE_DEPT.equals(dataScopeNum)) {
sqlString.append(" OR `sys_dept`.dept_id = '").append(deptId).append("' ");
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScopeNum)) {
sqlString.append(" OR `sys_dept`.dept_id IN ( SELECT dept_id FROM `sys_dept` WHERE dept_id = '")
.append(deptId)
.append("' or find_in_set( '")
.append(deptId)
.append("' , ancestors ) ) ");
}else if (DATA_SCOPE_SELF.equals(dataScopeNum)) {
//TODO 暂时有问题
sqlString.append(" OR `sys_user`.user_id = '").append(userId).append("' ");
}
}
if (StringUtils.isNotBlank(sqlString.toString())) {
if (where == null){
where = new HexValue(" 1 = 1 ");
}
sqlString.insert(0," AND (");
sqlString.append(")");
sqlString.delete(7, 9);
//判断是不是分页, 分页完成之后 清除权限标识
return CCJSqlParserUtil.parseCondExpression(where + sqlString.toString());
}else {
return where;
}
}
}
修改MybatisDataPermissionIntercept
- 修改
MybatisDataPermissionIntercept
在PageHelper
插件后面执行
/**
* @description 数据权限插件 配置
* @date 2022-04-01 17:01
*/
@AutoConfigureAfter(PageHelperAutoConfiguration.class)
@Configuration
public class MybatisInterceptorAutoConfiguration implements InitializingBean{
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Override
@PostConstruct
public void afterPropertiesSet() throws Exception {
//创建自定义mybatis拦截器,添加到chain的最后面
MybatisDataPermissionIntercept mybatisInterceptor = new MybatisDataPermissionIntercept();
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
//自己添加
configuration.addInterceptor(mybatisInterceptor);
}
}
}
配置spring.factories
- 在
resources
下面创建文件夹META-INF
,再创建一个文件spring.factories
,用spring.factories
是为了让自定义插件在PageHelperAutoConfiguration
之后注入到spring
中,后注入的先执行
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zhc.cloud.mybatis.intercept.MybatisInterceptorAutoConfiguration
代码示例
分页查询
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserPO> implements ISysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysDeptMapper sysDeptMapper;
/***
* 查询列表
* @return Result
*/
@Override
public Result<?> selectList(SysUserVO sysUserVO) {
SecurityUtils.startDataScope();
PageHelper.startPage(sysUserVO.getPageNum(), sysUserVO.getPageSize());
SysUserDTO sysUserDTO = new SysUserDTO();
BeanUtils.copyProperties(sysUserVO,sysUserDTO);
List<SysUserPO> sysUserPOS = sysUserMapper.selectUserList(sysUserDTO);
List<UserDTO> userDTOS = new ArrayList<>();
for (SysUserPO sysUserPO : sysUserPOS) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(sysUserPO,userDTO);
SysDeptPO sysDeptPO = sysDeptMapper.selectById(sysUserPO.getDeptId());
userDTO.setDeptName(sysDeptPO.getDeptName());
userDTO.setPhone(SensitiveInfoUtils.mobilePhone(userDTO.getPhone()));
userDTOS.add(userDTO);
}
PageInfo<UserDTO> pageInfo = new PageInfo<>(userDTOS);
return Result.success(pageInfo);
}
}
单个查询
@Service
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDeptPO> implements ISysDeptService {
@Autowired
private SysDeptMapper sysDeptPOMapper;
@Override
public Result<?> treeselect(SysDeptVO dept) {
SecurityUtils.startDataScope();
LambdaQueryWrapper<SysDeptPO> entityWrapper = new LambdaQueryWrapper<SysDeptPO>();
entityWrapper.eq((dept.getDeptId()!=null && dept.getDeptId() !=0),SysDeptPO::getDeptId,dept.getDeptId())
.eq((dept.getParentId()!=null &&dept.getParentId()!=0),SysDeptPO::getParentId,dept.getDeptId())
.like(StringUtils.isNotBlank(dept.getDeptName()),SysDeptPO::getDeptName,dept.getDeptName())
.eq(StringUtils.isNotBlank(dept.getStatus()),SysDeptPO::getStatus,dept.getStatus())
.orderByAsc(SysDeptPO::getParentId,SysDeptPO::getOrderNum);
List<SysDeptPO> sysDeptPO = sysDeptPOMapper.selectList(entityWrapper);
if(sysDeptPO == null){
return Result.success();
}
sysDeptPO.sort(Comparator.comparing(SysDeptPO::getParentId));
List<Long> tempList = new ArrayList<Long>();
for (SysDeptPO deptPO : sysDeptPO) {
tempList.add(deptPO.getParentId());
}
Long minParentId = Collections.min(tempList);
return Result.success(buildTree(sysDeptPO,minParentId));
}
}
注意点
- 使用
mybatis plus
自定义拦截器的时候一定要遵守规范,否则可能会引起一些查询异常. - 使用
mybatis plus
自定义拦截器可根据实际业务去进行扩展