1.自定义注解。在需要的拦截的方法上添加注解
package com.bimstudy.annotation;
import com.bimstudy.base.Constant;
import java.lang.annotation.*;
/**
* 数据域,用于标注哪个角色的数据域
*/
@Target( { ElementType.METHOD } )
@Retention( RetentionPolicy.RUNTIME )
@Documented
public @interface DataScope {
Constant.RolesRelation value() default Constant.RolesRelation.Train;
String scopeName() default "create_user_id";
/**
* 是否需要SQL过滤
*/
boolean needSqlFilter() default true;
}
@DataScope
List<User> findUsers();
自定义数据权限上下文信息
package com.bimstudy.annotation;
import lombok.Builder;
import lombok.Data;
/**
* 数据域上下文
*/
public class DataScopeRequestContext {
private final static ThreadLocal<Payload> requestHolder = new ThreadLocal<>();
public static void add(Payload payload){
requestHolder.set(payload);
}
public static void remove(){
requestHolder.remove();
}
public static Payload get(){
return requestHolder.get();
}
@Data
@Builder
public static class Payload{
/**
* 用户id
*/
private Long userId;
/**
* 租户id
*/
private Long tenantId;
/**
* 应用id
*/
private Long appId;
/**
* 仅为目标角色
*/
private Boolean isOnlyTargetRole;
/**
* 是否需要过滤
*/
private Boolean needSqlFilter;
/**
* 字段域
*/
private String scopeName;
}
}
2.定义spring aop拦截器,在拦截器中写入上下文内容
package com.bimstudy.aspect;
import com.bimstudy.annotation.DataScope;
import com.bimstudy.annotation.DataScopeRequestContext;
import com.bimstudy.service.IUserService;
import com.bimstudy.util.ComUtil;
import com.bimstudy.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 数据域拦截处理
*/
@Slf4j
@Aspect
@Component
@Order
public class DataScopeAop {
@Autowired
private IUserService userService;
@Around("@annotation(dataScope)")
public Object around(ProceedingJoinPoint jp, DataScope dataScope) throws Throwable {
Subject subject = SecurityUtils.getSubject();
Object principals = subject.getPrincipals();
if (!ComUtil.isEmpty(principals)){
String token = principals.toString();
Long userNo = Long.valueOf(JWTUtil.getUserNo(token));
Long loginFromAppId = Long.valueOf(JWTUtil.getLoginFromAppId(token));
Long tenantId = Long.valueOf(JWTUtil.getTenantId(token));
boolean needSqlFilter = dataScope.needSqlFilter();
boolean onlyTargetRole = userService.isOnlyTargetRole(userNo, tenantId, loginFromAppId,dataScope.value());
log.info("是否仅为直播管理员:{},needSqlFilter:{}",onlyTargetRole,needSqlFilter);
DataScopeRequestContext.add(DataScopeRequestContext.Payload.builder().userId(userNo).tenantId(tenantId).appId(loginFromAppId).isOnlyTargetRole(onlyTargetRole).needSqlFilter(needSqlFilter).scopeName(dataScope.scopeName()).build());
}
try{
Object result = jp.proceed();
return result;
}catch (Exception e){
log.error("DataScopeAop 执行业务逻辑异常,",e);
throw e;
}finally{
DataScopeRequestContext.remove();
}
}
}
3.自定义mybatis sql拦截器
package com.bimstudy.interceptor;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.core.parser.SqlInfo;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
import com.bimstudy.annotation.DataScopeRequestContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.*;
/**
* 数据权限拦截器
*/
@Slf4j
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataScopeInterceptor extends PaginationInterceptor {
/**
* COUNT SQL 解析
*/
private ISqlParser sqlParser;
/**
* 溢出总页数,设置第一页
*/
private boolean overflow = false;
/**
* 方言类型
*/
private String dialectType;
/**
* 方言实现类
*/
private String dialectClazz;
/**
* 定位结尾关键词
*/
private static final List<String> keyWords = Arrays.asList("group by","order by","limit");
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// SQL 解析
this.sqlParser(metaObject);
// 先判断是不是SELECT操作
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
// 针对定义了rowBounds,做为mapper接口方法的参数
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
Object paramObj = boundSql.getParameterObject();
List<ParameterMapping> mappings = new ArrayList<>(boundSql.getParameterMappings());
// 判断参数里是否有page对象
IPage page = null;
if (paramObj instanceof IPage) {
page = (IPage) paramObj;
} else if (paramObj instanceof Map) {
for (Object arg : ((Map) paramObj).values()) {
if (arg instanceof IPage) {
page = (IPage) arg;
break;
}
}
}
String originalSql = boundSql.getSql();
//去除分号
originalSql = originalSql.replace(";"," ");
DataScopeRequestContext.Payload payload = DataScopeRequestContext.get();
if (payload != null) {
Long userId = payload.getUserId();
if( payload.getNeedSqlFilter() && userId!=null){
//不需要分页的场合,如果 size 小于 0 返回结果集
if (null == page || page.getSize() < 0) {
String buildSql = concatExtCondition(originalSql, payload.getScopeName(), userId.toString(),false);
metaObject.setValue("delegate.boundSql.sql", buildSql);
metaObject.setValue("delegate.boundSql.parameterMappings", mappings);
return invocation.proceed();
}
String buildSql = concatExtCondition(originalSql, payload.getScopeName(), userId.toString(),true);
metaObject.setValue("delegate.boundSql.sql", buildSql);
metaObject.setValue("delegate.boundSql.parameterMappings", mappings);
//重新计算总页数,总条数信息
if (page.isSearchCount()) {
Connection connection = (Connection) invocation.getArgs()[0];
SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), sqlParser, buildSql);
String countSql = sqlInfo.getSql().substring(0, sqlInfo.getSql().lastIndexOf("LIMIT"));
Predicate mybatis_plus = mapped -> mapped.getProperty().startsWith("mybatis_plus_");
List limitParameter =
boundSql.getParameterMappings().stream().filter(mybatis_plus).collect(Collectors.toList());
boundSql.getParameterMappings().removeIf(mybatis_plus);
this.queryTotal(overflow, countSql, mappedStatement, boundSql, page, connection);
boundSql.getParameterMappings().addAll(limitParameter);
if (page.getTotal() <= 0) {
return invocation.proceed();
}
}
}
DataScopeRequestContext.remove();
}
return invocation.proceed();
}
/**
* 查询SQL拼接 create_user_id 过滤条件
* @param originalSql
* @param field
* @param value
* @return
*/
public static String concatExtCondition(String originalSql, String field, String value,boolean pageEnable) {
if (StringUtils.isNotEmpty(originalSql)) {
int whereIndex = originalSql.toLowerCase().indexOf("where");
String conditionString;
if (whereIndex <0) {
conditionString = " where " + field + " " + StringPool.EQUALS + " " + value + " ";
}else {
conditionString = " and " + field + " " + StringPool.EQUALS + " " + value + " ";
}
return insertConditionString(originalSql, conditionString);
}
return originalSql;
}
private static String insertConditionString(String originalSql, String conditionString) {
StringBuilder buildSql = new StringBuilder(originalSql);
int keyWordIndex = getKeyWordIndex(originalSql);
if (keyWordIndex > 0) {
buildSql.insert(keyWordIndex, conditionString);
} else {
buildSql.append(conditionString);
}
return buildSql.toString();
}
private static int getKeyWordIndex(String originalSql) {
int keyWordIndex=-1;
for (int i = 0; i < keyWords.size(); i++) {
String keyWord = keyWords.get(i);
keyWordIndex = originalSql.toLowerCase().indexOf(keyWord);
if (keyWordIndex >0) {
break;
}
}
return keyWordIndex;
}
@Override
public Object plugin(Object target) {
DataScopeRequestContext.Payload payload = DataScopeRequestContext.get();
if (target instanceof StatementHandler && payload !=null && payload.getNeedSqlFilter() && payload.getIsOnlyTargetRole()) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
String dialectType = properties.getProperty("dialectType");
String dialectClazz = properties.getProperty("dialectClazz");
if (StringUtils.isNotEmpty(dialectType)) {
this.dialectType = dialectType;
}
if (StringUtils.isNotEmpty(dialectClazz)) {
this.dialectClazz = dialectClazz;
}
}
}