提交 3f3def6f 编写于 作者: 云彩ing

Merge branch 'dev' of git.zhlh6.cn:pig-mesh/pig into dev

......@@ -16,6 +16,7 @@
package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.auth.converter.CustomAccessTokenConverter;
import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.security.component.PigWebResponseExceptionTranslator;
......@@ -63,10 +64,7 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
PigClientDetailsService clientDetailsService = new PigClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
clients.withClientDetails(clientDetailsService);
clients.withClientDetails(pigClientDetailsService());
}
@Override
......@@ -80,7 +78,8 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
.tokenEnhancer(tokenEnhancer()).userDetailsService(userDetailsService)
.authenticationManager(authenticationManager).reuseRefreshTokens(false)
.pathMapping("/oauth/confirm_access", "/token/confirm_access")
.exceptionTranslator(new PigWebResponseExceptionTranslator());
.exceptionTranslator(new PigWebResponseExceptionTranslator())
.accessTokenConverter(new CustomAccessTokenConverter(pigClientDetailsService()));
}
@Bean
......@@ -104,4 +103,12 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
};
}
@Bean
public PigClientDetailsService pigClientDetailsService() {
PigClientDetailsService clientDetailsService = new PigClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
return clientDetailsService;
}
}
package com.pig4cloud.pig.auth.converter;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.security.service.PigClientDetailsService;
import com.pig4cloud.pig.common.security.service.PigUser;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import java.util.Map;
/**
* @author hccake
*/
@RequiredArgsConstructor
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
final PigClientDetailsService pigClientDetailsService;
@Override
@SuppressWarnings("unchecked")
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map<String, Object> response = (Map<String, Object>) super.convertAccessToken(token, authentication);
ClientDetails clientDetails = pigClientDetailsService
.loadClientByClientId(authentication.getOAuth2Request().getClientId());
if (clientDetails != null && clientDetails.getScope().contains("read_data_scope")) {
PigUser principal = (PigUser) authentication.getPrincipal();
response.put(SecurityConstants.DETAILS_USER_DATA_SCOPE, principal.getUserDataScope());
}
return response;
}
}
......@@ -25,6 +25,9 @@
<fastjson.version>1.2.75</fastjson.version>
<swagger.core.version>1.5.24</swagger.core.version>
<mybatis-plus.version>3.4.3.3</mybatis-plus.version>
<mybatis.version>3.5.7</mybatis.version>
<jsqlparser.version>4.1</jsqlparser.version>
<rocksdbjni.version>5.18.3</rocksdbjni.version>
<nacos.version>2.0.3</nacos.version>
<excel.version>1.0.0</excel.version>
<oss.version>1.0.1</oss.version>
......@@ -38,6 +41,11 @@
<artifactId>pig-common-core</artifactId>
<version>${pig.common.version}</version>
</dependency>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-datascope</artifactId>
<version>${pig.common.version}</version>
</dependency>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-datasource</artifactId>
......@@ -123,12 +131,22 @@
<artifactId>oss-spring-boot-starter</artifactId>
<version>${oss.version}</version>
</dependency>
<!--mybatis-plus-->
<!--orm 相关-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser.version}</version>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -109,6 +109,11 @@ public interface SecurityConstants {
*/
String DETAILS_LICENSE = "license";
/**
* 用户数据权限信息
*/
String DETAILS_USER_DATA_SCOPE = "user_data_scope";
/**
* 验证码有效期,默认 60秒
*/
......
package com.pig4cloud.pig.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据权限范围类型
* @author hccake
*/
@Getter
@AllArgsConstructor
public enum DataScopeTypeEnum {
/**
* 查询全部数据
*/
ALL(0),
/**
* 本人
*/
SELF(1),
/**
* 本人及子级
*/
SELF_CHILD_LEVEL(2),
/**
* 本级
*/
LEVEL(3),
/**
* 本级及子级
*/
LEVEL_CHILD_LEVEL(4),
/**
* 自定义
*/
CUSTOM(5);
/**
* 类型
*/
private final Integer type;
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>pig-common</artifactId>
<groupId>com.pig4cloud</groupId>
<version>3.3.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pig-common-datascope</artifactId>
<dependencies>
<!-- slf4j日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.pigcloud.pig.common.datascope;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import java.util.Collection;
/**
* @author Hccake 2020/9/28
* @version 1.0
*/
public interface DataScope {
/**
* 数据所对应的资源
* @return 资源标识
*/
String getResource();
/**
* 该资源相关的所有表,推荐使用 Set 类型。 <br/>
* 如需忽略表名大小写判断,则可以使用 TreeSet,并设置忽略大小写的自定义Comparator。 <br/>
* eg. new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
* @return tableNames
*/
Collection<String> getTableNames();
/**
* 根据表名和表别名,动态生成的 where/or 筛选条件
* @param tableName 表名
* @param tableAlias 表别名,可能为空
* @return 数据规则表达式
*/
Expression getExpression(String tableName, Alias tableAlias);
}
package com.pigcloud.pig.common.datascope;
import com.pigcloud.pig.common.datascope.handler.DataPermissionHandler;
import com.pigcloud.pig.common.datascope.handler.DefaultDataPermissionHandler;
import com.pigcloud.pig.common.datascope.interceptor.DataPermissionAnnotationAdvisor;
import com.pigcloud.pig.common.datascope.interceptor.DataPermissionInterceptor;
import com.pigcloud.pig.common.datascope.processor.DataScopeSqlProcessor;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
* @author hccake
*/
@RequiredArgsConstructor
@ConditionalOnBean(DataScope.class)
public class DataScopeAutoConfiguration {
/**
* 数据权限处理器
* @param dataScopeList 需要控制的数据范围集合
* @return DataPermissionHandler
*/
@Bean
@ConditionalOnMissingBean
public DataPermissionHandler dataPermissionHandler(List<DataScope> dataScopeList) {
return new DefaultDataPermissionHandler(dataScopeList);
}
/**
* 数据权限注解 Advisor,用于处理数据权限的链式调用关系
* @return DataPermissionAnnotationAdvisor
*/
@Bean
@ConditionalOnMissingBean(DataPermissionAnnotationAdvisor.class)
public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
return new DataPermissionAnnotationAdvisor();
}
/**
* mybatis 拦截器,用于拦截处理 sql
* @param dataPermissionHandler 数据权限处理器
* @return DataPermissionInterceptor
*/
@Bean
@ConditionalOnMissingBean
public DataPermissionInterceptor dataPermissionInterceptor(DataPermissionHandler dataPermissionHandler) {
return new DataPermissionInterceptor(new DataScopeSqlProcessor(), dataPermissionHandler);
}
}
package com.pigcloud.pig.common.datascope.annotation;
import java.lang.annotation.*;
/**
* 数据权限注解,注解在 Mapper类 或者 对应方法上 用于提供该 mapper 对应表,所需控制的实体信息
* @author Hccake 2020/9/27
* @version 1.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
/**
* 当前类或方法是否忽略数据权限
* @return boolean 默认返回 false
*/
boolean ignore() default false;
/**
* 仅对指定资源类型进行数据权限控制,只在开启情况下有效,当该数组有值时,exclude不生效
* @see DataPermission#excludeResources
* @return 资源类型数组
*/
String[] includeResources() default {};
/**
* 对指定资源类型跳过数据权限控制,只在开启情况下有效,当该includeResources有值时,exclude不生效
* @see DataPermission#includeResources
* @return 资源类型数组
*/
String[] excludeResources() default {};
}
package com.pigcloud.pig.common.datascope.handler;
import com.pigcloud.pig.common.datascope.DataScope;
import java.util.List;
/**
* 数据权限处理器
*
* @author Hccake 2020/9/28
* @version 1.0
*/
public interface DataPermissionHandler {
/**
* 系统配置的所有的数据范围
* @return 数据范围集合
*/
List<DataScope> dataScopes();
/**
* 根据权限注解过滤后的数据范围集合
* @param mappedStatementId Mapper方法ID
* @return 数据范围集合
*/
List<DataScope> filterDataScopes(String mappedStatementId);
/**
* 是否忽略权限控制,用于及早的忽略控制,例如管理员直接放行,而不必等到DataScope中再进行过滤处理,提升效率
* @return boolean true: 忽略,false: 进行权限控制
* @param mappedStatementId Mapper方法ID
*/
boolean ignorePermissionControl(String mappedStatementId);
}
package com.pigcloud.pig.common.datascope.handler;
import com.pigcloud.pig.common.datascope.DataScope;
import com.pigcloud.pig.common.datascope.annotation.DataPermission;
import com.pigcloud.pig.common.datascope.holder.DataPermissionAnnotationHolder;
import lombok.RequiredArgsConstructor;
import java.util.*;
import java.util.stream.Collectors;
/**
* 默认的数据权限控制处理器
*
* @author Hccake 2021/1/27
* @version 1.0
*/
@RequiredArgsConstructor
public class DefaultDataPermissionHandler implements DataPermissionHandler {
private final List<DataScope> dataScopes;
/**
* 系统配置的所有的数据范围
* @return 数据范围集合
*/
@Override
public List<DataScope> dataScopes() {
return dataScopes;
}
/**
* 系统配置的所有的数据范围
* @param mappedStatementId Mapper方法ID
* @return 数据范围集合
*/
@Override
public List<DataScope> filterDataScopes(String mappedStatementId) {
if (this.dataScopes == null || this.dataScopes.isEmpty()) {
return new ArrayList<>();
}
// 获取当前方法对应的权限注解,根据注解进行数据范围控制的过滤
DataPermission dataPermission = DataPermissionAnnotationHolder.peek();
if (dataPermission == null) {
return dataScopes;
}
if (dataPermission.ignore()) {
return new ArrayList<>();
}
// 当指定了只包含的资源时,只对该资源的DataScope
if (dataPermission.includeResources().length > 0) {
Set<String> a = new HashSet<>(Arrays.asList(dataPermission.includeResources()));
return dataScopes.stream().filter(x -> a.contains(x.getResource())).collect(Collectors.toList());
}
// 当未指定只包含的资源,且指定了排除的资源时,则排除此部分资源的 DataScope
if (dataPermission.excludeResources().length > 0) {
Set<String> a = new HashSet<>(Arrays.asList(dataPermission.excludeResources()));
return dataScopes.stream().filter(x -> !a.contains(x.getResource())).collect(Collectors.toList());
}
return dataScopes;
}
/**
* 是否忽略权限控制,默认不忽略
* @param mappedStatementId Mapper方法ID
* @return always false
*/
@Override
public boolean ignorePermissionControl(String mappedStatementId) {
return false;
}
}
package com.pigcloud.pig.common.datascope.holder;
import com.pigcloud.pig.common.datascope.annotation.DataPermission;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* 数据权限注解的持有者,使用栈存储调用链中各方法对应数据权限注解
*
* @author hccake
*/
public final class DataPermissionAnnotationHolder {
private DataPermissionAnnotationHolder() {
}
/**
* 使用栈存储 DataPermission,便于在方法嵌套调用时使用不同的数据权限控制。
*/
private static final ThreadLocal<Deque<DataPermission>> DATA_PERMISSIONS = ThreadLocal.withInitial(ArrayDeque::new);
/**
* 获取当前的 DataPermission 注解
* @return DataPermission
*/
public static DataPermission peek() {
return DATA_PERMISSIONS.get().peek();
}
/**
* 入栈一个 DataPermission 注解
* @return DataPermission
*/
public static DataPermission push(DataPermission dataPermission) {
DATA_PERMISSIONS.get().push(dataPermission);
return dataPermission;
}
/**
* 弹出最顶部 DataPermission
*/
public static void poll() {
Deque<DataPermission> deque = DATA_PERMISSIONS.get();
// 当没有元素时,清空 ThreadLocal
if (deque.poll() == null) {
DATA_PERMISSIONS.remove();
}
}
/**
* 清除 TreadLocal
*/
public static void clear() {
DATA_PERMISSIONS.remove();
}
}
package com.pigcloud.pig.common.datascope.holder;
import com.pigcloud.pig.common.datascope.DataScope;
import java.util.List;
/**
* DataScope 持有者。 方便解析 SQL 时的参数透传
*
* @author hccake
*/
public final class DataScopeHolder {
private DataScopeHolder() {
}
private static final ThreadLocal<List<DataScope>> DATA_SCOPES = new ThreadLocal<>();
/**
* get dataScope
* @return dataScopes
*/
public static List<DataScope> get() {
return DATA_SCOPES.get();
}
/**
* 添加 dataScope
*/
public static void set(List<DataScope> dataScopes) {
DATA_SCOPES.set(dataScopes);
}
/**
* 删除 dataScope
*/
public static void remove() {
DATA_SCOPES.remove();
}
}
package com.pigcloud.pig.common.datascope.interceptor;
import com.pigcloud.pig.common.datascope.annotation.DataPermission;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
/**
* @author hccake
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {
private final Advice advice;
private final Pointcut pointcut;
public DataPermissionAnnotationAdvisor() {
this.advice = new DataPermissionAnnotationInterceptor();
this.pointcut = buildPointcut();
}
protected Pointcut buildPointcut() {
Pointcut cpc = new AnnotationMatchingPointcut(DataPermission.class, true);
Pointcut mpc = new AnnotationMatchingPointcut(null, DataPermission.class, true);
return new ComposablePointcut(cpc).union(mpc);
}
}
package com.pigcloud.pig.common.datascope.interceptor;
import com.pigcloud.pig.common.datascope.annotation.DataPermission;
import com.pigcloud.pig.common.datascope.holder.DataPermissionAnnotationHolder;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
* DataPermission注解的拦截器,在执行方法前将当前方法的对应注解压栈,执行后弹出注解
*
* @author hccake
*/
public class DataPermissionAnnotationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 当前方法
Method method = methodInvocation.getMethod();
// 获取执行类
Object invocationThis = methodInvocation.getThis();
Class<?> clazz = invocationThis != null ? invocationThis.getClass() : method.getDeclaringClass();
// 寻找对应的 DataPermission 注解属性
DataPermission dataPermission = DataPermissionFinder.findDataPermission(method, clazz);
DataPermissionAnnotationHolder.push(dataPermission);
try {
return methodInvocation.proceed();
}
finally {
DataPermissionAnnotationHolder.poll();
}
}
}
package com.pigcloud.pig.common.datascope.interceptor;
import com.pigcloud.pig.common.datascope.annotation.DataPermission;
import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.AnnotatedElementUtils;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* {@link DataPermission} 注解的查找者。用于查询当前方法对应的 DataPermission 注解环境,当方法上没有找到时,会去类上寻找。
*
* @author hccake
*/
@DataPermission
public final class DataPermissionFinder {
private DataPermissionFinder() {
}
private static final Map<Object, DataPermission> DATA_PERMISSION_CACHE = new ConcurrentHashMap<>(1024);
/**
* 提供一个默认的空值注解,用于缓存空值占位使用
*/
private static final DataPermission EMPTY_DATA_PERMISSION = DataPermissionFinder.class
.getAnnotation(DataPermission.class);
/**
* 缓存的 key 值
* @param method 方法
* @param clazz 类
* @return key
*/
private static Object getCacheKey(Method method, Class<?> clazz) {
return new MethodClassKey(method, clazz);
}
/**
* 从缓存中获取数据权限注解 优先获取方法上的注解,再获取类上的注解
* @param method 当前方法
* @param clazz 当前类
* @return 当前方法有效的数据权限注解
*/
public static DataPermission findDataPermission(Method method, Class<?> clazz) {
Object methodKey = getCacheKey(method, clazz);
if (DATA_PERMISSION_CACHE.containsKey(methodKey)) {
DataPermission dataPermission = DATA_PERMISSION_CACHE.get(methodKey);
// 判断是否和缓存的空注解是同一个对象
return EMPTY_DATA_PERMISSION == dataPermission ? null : dataPermission;
}
// 先查方法,如果方法上没有,则使用类上
DataPermission dataPermission = AnnotatedElementUtils.findMergedAnnotation(method, DataPermission.class);
if (dataPermission == null) {
dataPermission = AnnotatedElementUtils.findMergedAnnotation(clazz, DataPermission.class);
}
DATA_PERMISSION_CACHE.put(methodKey, dataPermission == null ? EMPTY_DATA_PERMISSION : dataPermission);
return dataPermission;
}
}
package com.pigcloud.pig.common.datascope.interceptor;
import com.pigcloud.pig.common.datascope.DataScope;
import com.pigcloud.pig.common.datascope.handler.DataPermissionHandler;
import com.pigcloud.pig.common.datascope.processor.DataScopeSqlProcessor;
import com.pigcloud.pig.common.datascope.util.PluginUtils;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.List;
/**
* 数据权限拦截器
*
* @author Hccake 2020/9/28
* @version 1.0
*/
@RequiredArgsConstructor
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class DataPermissionInterceptor implements Interceptor {
private final DataScopeSqlProcessor dataScopeSqlProcessor;
private final DataPermissionHandler dataPermissionHandler;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 第一版,测试用
Object target = invocation.getTarget();
StatementHandler sh = (StatementHandler) target;
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
String mappedStatementId = ms.getId();
// 根据用户权限判断是否需要拦截,例如管理员可以查看所有,则直接放行
if (dataPermissionHandler.ignorePermissionControl(mappedStatementId)) {
return invocation.proceed();
}
List<DataScope> dataScopes = dataPermissionHandler.filterDataScopes(mappedStatementId);
if (dataScopes == null || dataScopes.isEmpty()) {
return invocation.proceed();
}
// 根据 DataScopes 进行数据权限的 sql 处理
if (sct == SqlCommandType.SELECT) {
mpBs.sql(dataScopeSqlProcessor.parserSingle(mpBs.sql(), dataScopes));
}
else if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
mpBs.sql(dataScopeSqlProcessor.parserMulti(mpBs.sql(), dataScopes));
}
// 执行 sql
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
}
package com.pigcloud.pig.common.datascope.parser;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
/**
* https://github.com/JSQLParser/JSqlParser
*
* @author miemie hccake
* @since 2020-06-22
*/
@Slf4j
public abstract class JsqlParserSupport {
public String parserSingle(String sql, Object obj) {
if (log.isDebugEnabled()) {
log.debug("original SQL: " + sql);
}
try {
Statement statement = CCJSqlParserUtil.parse(sql);
return processParser(statement, 0, sql, obj);
}
catch (JSQLParserException e) {
throw new RuntimeException(String.format("Failed to process, Error SQL: %s", sql), e);
}
}
public String parserMulti(String sql, Object obj) {
if (log.isDebugEnabled()) {
log.debug("original SQL: " + sql);
}
try {
// fixed github pull/295
StringBuilder sb = new StringBuilder();
Statements statements = CCJSqlParserUtil.parseStatements(sql);
int i = 0;
for (Statement statement : statements.getStatements()) {
if (i > 0) {
sb.append(";");
}
sb.append(processParser(statement, i, sql, obj));
i++;
}
return sb.toString();
}
catch (JSQLParserException e) {
throw new RuntimeException(String.format("Failed to process, Error SQL: %s", sql), e);
}
}
/**
* 执行 SQL 解析
* @param statement JsqlParser Statement
* @return sql
*/
protected String processParser(Statement statement, int index, String sql, Object obj) {
if (log.isDebugEnabled()) {
log.debug("SQL to parse, SQL: " + sql);
}
if (statement instanceof Insert) {
this.processInsert((Insert) statement, index, sql, obj);
}
else if (statement instanceof Select) {
this.processSelect((Select) statement, index, sql, obj);
}
else if (statement instanceof Update) {
this.processUpdate((Update) statement, index, sql, obj);
}
else if (statement instanceof Delete) {
this.processDelete((Delete) statement, index, sql, obj);
}
sql = statement.toString();
if (log.isDebugEnabled()) {
log.debug("parse the finished SQL: " + sql);
}
return sql;
}
/**
* 新增
*/
protected void processInsert(Insert insert, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}
/**
* 删除
*/
protected void processDelete(Delete delete, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}
/**
* 更新
*/
protected void processUpdate(Update update, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}
/**
* 查询
*/
protected void processSelect(Select select, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}
}
package com.pigcloud.pig.common.datascope.processor;
import com.pigcloud.pig.common.datascope.DataScope;
import com.pigcloud.pig.common.datascope.holder.DataScopeHolder;
import com.pigcloud.pig.common.datascope.parser.JsqlParserSupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import java.util.List;
import java.util.Objects;
/**
* 数据权限 sql 处理器 参考 mybatis-plus 租户拦截器,解析 sql where 部分,进行查询表达式注入
*
* @author Hccake 2020/9/26
* @version 1.0
*/
@RequiredArgsConstructor
@Slf4j
public class DataScopeSqlProcessor extends JsqlParserSupport {
/**
* select 类型SQL处理
* @param select jsqlparser Statement Select
*/
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
List<DataScope> dataScopes = (List<DataScope>) obj;
try {
// dataScopes 放入 ThreadLocal 方便透传
DataScopeHolder.set(dataScopes);
processSelectBody(select.getSelectBody());
List<WithItem> withItemsList = select.getWithItemsList();
if (withItemsList != null && !withItemsList.isEmpty()) {
withItemsList.forEach(this::processSelectBody);
}
}
finally {
// 必须清空 ThreadLocal
DataScopeHolder.remove();
}
}
protected void processSelectBody(SelectBody selectBody) {
if (selectBody == null) {
return;
}
if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody);
}
else if (selectBody instanceof WithItem) {
WithItem withItem = (WithItem) selectBody;
processSelectBody(withItem.getSubSelect().getSelectBody());
}
else {
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodys = operationList.getSelects();
if (selectBodys != null && !selectBodys.isEmpty()) {
selectBodys.forEach(this::processSelectBody);
}
}
}
/**
* insert 类型SQL处理
* @param insert jsqlparser Statement Insert
*/
@Override
protected void processInsert(Insert insert, int index, String sql, Object obj) {
// insert 暂时不处理
}
/**
* update 类型SQL处理
* @param update jsqlparser Statement Update
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
List<DataScope> dataScopes = (List<DataScope>) obj;
try {
// dataScopes 放入 ThreadLocal 方便透传
DataScopeHolder.set(dataScopes);
update.setWhere(this.injectExpression(update.getWhere(), update.getTable()));
}
finally {
// 必须清空 ThreadLocal
DataScopeHolder.remove();
}
}
/**
* delete 类型SQL处理
* @param delete jsqlparser Statement Delete
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
List<DataScope> dataScopes = (List<DataScope>) obj;
try {
// dataScopes 放入 ThreadLocal 方便透传
DataScopeHolder.set(dataScopes);
delete.setWhere(this.injectExpression(delete.getWhere(), delete.getTable()));
}
finally {
// 必须清空 ThreadLocal
DataScopeHolder.remove();
}
}
/**
* 处理 PlainSelect
*/
protected void processPlainSelect(PlainSelect plainSelect) {
FromItem fromItem = plainSelect.getFromItem();
Expression where = plainSelect.getWhere();
processWhereSubSelect(where);
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
// #1186 github
plainSelect.setWhere(injectExpression(where, fromTable));
}
else {
processFromItem(fromItem);
}
// #3087 github
List<SelectItem> selectItems = plainSelect.getSelectItems();
if (selectItems != null && !selectItems.isEmpty()) {
selectItems.forEach(this::processSelectItem);
}
List<Join> joins = plainSelect.getJoins();
if (joins != null && !joins.isEmpty()) {
joins.forEach(j -> {
processJoin(j);
processFromItem(j.getRightItem());
});
}
}
/**
* 处理where条件内的子查询
* <p>
* 支持如下: 1. in 2. = 3. > 4. < 5. >= 6. <= 7. <> 8. EXISTS 9. NOT EXISTS
* <p>
* 前提条件: 1. 子查询必须放在小括号中 2. 子查询一般放在比较操作符的右边
* @param where where 条件
*/
protected void processWhereSubSelect(Expression where) {
if (where == null) {
return;
}
if (where instanceof FromItem) {
processFromItem((FromItem) where);
return;
}
if (where.toString().indexOf("SELECT") > 0) {
// 有子查询
if (where instanceof BinaryExpression) {
// 比较符号 , and , or , 等等
BinaryExpression expression = (BinaryExpression) where;
processWhereSubSelect(expression.getLeftExpression());
processWhereSubSelect(expression.getRightExpression());
}
else if (where instanceof InExpression) {
// in
InExpression expression = (InExpression) where;
ItemsList itemsList = expression.getRightItemsList();
if (itemsList instanceof SubSelect) {
processSelectBody(((SubSelect) itemsList).getSelectBody());
}
}
else if (where instanceof ExistsExpression) {
// exists
ExistsExpression expression = (ExistsExpression) where;
processWhereSubSelect(expression.getRightExpression());
}
else if (where instanceof NotExpression) {
// not exists
NotExpression expression = (NotExpression) where;
processWhereSubSelect(expression.getExpression());
}
else if (where instanceof Parenthesis) {
Parenthesis expression = (Parenthesis) where;
processWhereSubSelect(expression.getExpression());
}
}
}
protected void processSelectItem(SelectItem selectItem) {
if (selectItem instanceof SelectExpressionItem) {
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
if (selectExpressionItem.getExpression() instanceof SubSelect) {
processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
}
else if (selectExpressionItem.getExpression() instanceof Function) {
processFunction((Function) selectExpressionItem.getExpression());
}
}
}
/**
* 处理函数
* <p>
* 支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)
* <p>
* <p>
* fixed gitee pulls/141
* </p>
* @param function
*/
protected void processFunction(Function function) {
ExpressionList parameters = function.getParameters();
if (parameters != null) {
parameters.getExpressions().forEach(expression -> {
if (expression instanceof SubSelect) {
processSelectBody(((SubSelect) expression).getSelectBody());
}
else if (expression instanceof Function) {
processFunction((Function) expression);
}
});
}
}
/**
* 处理子查询等
*/
protected void processFromItem(FromItem fromItem) {
if (fromItem instanceof SubJoin) {
SubJoin subJoin = (SubJoin) fromItem;
if (subJoin.getJoinList() != null) {
subJoin.getJoinList().forEach(this::processJoin);
}
if (subJoin.getLeft() != null) {
processFromItem(subJoin.getLeft());
}
}
else if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
}
else if (fromItem instanceof ValuesList) {
log.debug("Perform a subquery, if you do not give us feedback");
}
else if (fromItem instanceof LateralSubSelect) {
LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
if (lateralSubSelect.getSubSelect() != null) {
SubSelect subSelect = lateralSubSelect.getSubSelect();
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
}
}
}
/**
* 处理联接语句
*/
protected void processJoin(Join join) {
if (join.getRightItem() instanceof Table) {
Table fromTable = (Table) join.getRightItem();
join.setOnExpression(injectExpression(join.getOnExpression(), fromTable));
}
}
/**
* 根据 DataScope ,将数据过滤的表达式注入原本的 where/or 条件
* @param currentExpression Expression where/or
* @param table 表信息
* @return 修改后的 where/or 条件
*/
private Expression injectExpression(Expression currentExpression, Table table) {
String tableName = table.getName();
List<DataScope> dataScopes = DataScopeHolder.get();
Expression dataFilterExpression = dataScopes.stream().filter(x -> x.getTableNames().contains(tableName))
.map(x -> x.getExpression(tableName, table.getAlias())).filter(Objects::nonNull)
.reduce(AndExpression::new).orElse(null);
if (currentExpression == null) {
return dataFilterExpression;
}
if (dataFilterExpression == null) {
return currentExpression;
}
if (currentExpression instanceof OrExpression) {
return new AndExpression(new Parenthesis(currentExpression), dataFilterExpression);
}
else {
return new AndExpression(currentExpression, dataFilterExpression);
}
}
/**
* 根据当前表是否有别名,动态对字段名前添加表别名 eg. 表名: table_1 as t 原始字段:column1 返回: t.column1
* @param table 表信息
* @param column 字段名
* @return 原始字段名,或者添加了表别名的字段名
*/
protected Column getAliasColumn(Table table, String column) {
StringBuilder columnBuilder = new StringBuilder();
if (table.getAlias() != null) {
columnBuilder.append(table.getAlias().getName()).append(".");
}
columnBuilder.append(column);
return new Column(columnBuilder.toString());
}
}
package com.pigcloud.pig.common.datascope.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author Hccake 2021/1/27
* @version 1.0
*/
public final class AnnotationUtil {
private AnnotationUtil() {
}
/**
* 获取数据权限注解 优先获取方法上的注解,再获取类上的注解
* @param mappedStatementId 类名.方法名
* @return 数据权限注解
*/
public static <A extends Annotation> A findAnnotationByMappedStatementId(String mappedStatementId,
Class<A> aClass) {
if (mappedStatementId == null || "".equals(mappedStatementId)) {
return null;
}
// 1.得到类路径和方法路径
int lastIndexOfDot = mappedStatementId.lastIndexOf(".");
if (lastIndexOfDot < 0) {
return null;
}
String className = mappedStatementId.substring(0, lastIndexOfDot);
String methodName = mappedStatementId.substring(lastIndexOfDot + 1);
if ("".equals(className) || "".equals(methodName)) {
return null;
}
// 2.字节码
Class<?> clazz = null;
try {
clazz = Class.forName(className);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (clazz == null) {
return null;
}
A annotation = null;
// 3.得到方法上的注解
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String name = method.getName();
if (methodName.equals(name)) {
annotation = method.getAnnotation(aClass);
break;
}
}
if (annotation == null) {
annotation = clazz.getAnnotation(aClass);
}
return annotation;
}
}
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <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 com.pigcloud.pig.common.datascope.util;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 插件工具类
*
* @author TaoYu , hubin
* @since 2017-06-20
*/
public abstract class PluginUtils {
public static final String DELEGATE_BOUNDSQL_SQL = "delegate.boundSql.sql";
/**
* 获得真正的处理对象,可能多层代理.
*/
@SuppressWarnings("unchecked")
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
/**
* 给 BoundSql 设置 additionalParameters
* @param boundSql BoundSql
* @param additionalParameters additionalParameters
*/
public static void setAdditionalParameter(BoundSql boundSql, Map<String, Object> additionalParameters) {
additionalParameters.forEach(boundSql::setAdditionalParameter);
}
public static MPBoundSql mpBoundSql(BoundSql boundSql) {
return new MPBoundSql(boundSql);
}
public static MPStatementHandler mpStatementHandler(StatementHandler statementHandler) {
statementHandler = realTarget(statementHandler);
MetaObject object = SystemMetaObject.forObject(statementHandler);
return new MPStatementHandler(SystemMetaObject.forObject(object.getValue("delegate")));
}
/**
* {@link org.apache.ibatis.executor.statement.BaseStatementHandler}
*/
public static class MPStatementHandler {
private final MetaObject statementHandler;
MPStatementHandler(MetaObject statementHandler) {
this.statementHandler = statementHandler;
}
public ParameterHandler parameterHandler() {
return get("parameterHandler");
}
public MappedStatement mappedStatement() {
return get("mappedStatement");
}
public Executor executor() {
return get("executor");
}
public MPBoundSql mPBoundSql() {
return new MPBoundSql(boundSql());
}
public BoundSql boundSql() {
return get("boundSql");
}
public Configuration configuration() {
return get("configuration");
}
@SuppressWarnings("unchecked")
private <T> T get(String property) {
return (T) statementHandler.getValue(property);
}
}
/**
* {@link BoundSql}
*/
public static class MPBoundSql {
private final MetaObject boundSql;
private final BoundSql delegate;
MPBoundSql(BoundSql boundSql) {
this.delegate = boundSql;
this.boundSql = SystemMetaObject.forObject(boundSql);
}
public String sql() {
return delegate.getSql();
}
public void sql(String sql) {
boundSql.setValue("sql", sql);
}
public List<ParameterMapping> parameterMappings() {
List<ParameterMapping> parameterMappings = delegate.getParameterMappings();
return new ArrayList<>(parameterMappings);
}
public void parameterMappings(List<ParameterMapping> parameterMappings) {
boundSql.setValue("parameterMappings", Collections.unmodifiableList(parameterMappings));
}
public Object parameterObject() {
return get("parameterObject");
}
public Map<String, Object> additionalParameters() {
return get("additionalParameters");
}
@SuppressWarnings("unchecked")
private <T> T get(String property) {
return (T) boundSql.getValue(property);
}
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pigcloud.pig.common.datascope.DataScopeAutoConfiguration
package com.pigcloud.pig.common.datascope.test;
import com.pigcloud.pig.common.datascope.DataScope;
import com.pigcloud.pig.common.datascope.handler.DefaultDataPermissionHandler;
import com.pigcloud.pig.common.datascope.handler.DataPermissionHandler;
import com.pigcloud.pig.common.datascope.processor.DataScopeSqlProcessor;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import org.junit.jupiter.api.Test;
import org.springframework.util.Assert;
import java.util.*;
/**
* @author Hccake 2020/9/28
* @version 1.0
*/
class SqlParseTest {
@Test
void test() {
DataScope dataScope = new DataScope() {
final String columnId = "order_id";
@Override
public String getResource() {
return "order";
}
@Override
public Collection<String> getTableNames() {
Set<String> tableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
tableNames.addAll(Arrays.asList("t_order", "t_order_info"));
return tableNames;
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
Column column = new Column(tableAlias == null ? columnId : tableAlias.getName() + "." + columnId);
ExpressionList expressionList = new ExpressionList();
expressionList.setExpressions(Arrays.asList(new StringValue("1"), new StringValue("2")));
return new InExpression(column, expressionList);
}
};
List<DataScope> dataScopes = new ArrayList<>();
dataScopes.add(dataScope);
DataPermissionHandler dataPermissionHandler = new DefaultDataPermissionHandler(dataScopes) {
@Override
public boolean ignorePermissionControl(String mappedStatementId) {
return false;
}
};
DataScopeSqlProcessor dataScopeSqlProcessor = new DataScopeSqlProcessor();
// DataScopeHolder.putDataScope("order", dataScope);
String sql = "select o.order_id,o.order_name,oi.order_price "
+ "from t_ORDER o left join t_order_info oi on o.order_id = oi.order_id "
+ "where oi.order_price > 100";
String parseSql = dataScopeSqlProcessor.parserSingle(sql, dataPermissionHandler.dataScopes());
System.out.println(parseSql);
String trueSql = "SELECT o.order_id, o.order_name, oi.order_price FROM t_ORDER o LEFT JOIN t_order_info oi ON o.order_id = oi.order_id AND oi.order_id IN ('1', '2') WHERE oi.order_price > 100 AND o.order_id IN ('1', '2')";
Assert.isTrue(trueSql.equals(parseSql), "sql 数据权限解析异常");
}
}
......@@ -17,6 +17,7 @@
package com.pig4cloud.pig.common.security.component;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.security.datascope.UserDataScope;
import com.pig4cloud.pig.common.security.service.PigUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
......@@ -26,8 +27,11 @@ import org.springframework.security.oauth2.provider.token.UserAuthenticationConv
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author lengleng
......@@ -69,7 +73,17 @@ public class PigUserAuthenticationConverter implements UserAuthenticationConvert
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
Integer id = (Integer) map.get(SecurityConstants.DETAILS_USER_ID);
Integer deptId = (Integer) map.get(SecurityConstants.DETAILS_DEPT_ID);
PigUser user = new PigUser(id, deptId, username, N_A, true, true, true, true, authorities);
UserDataScope userDataScope = new UserDataScope();
Object value = map.get(SecurityConstants.DETAILS_USER_DATA_SCOPE);
if (value != null) {
Map<String, ?> userDataScopeMap = (Map) value;
userDataScope.setAllScope((boolean) userDataScopeMap.get("allScope"));
userDataScope.setOnlySelf((boolean) userDataScopeMap.get("onlySelf"));
userDataScope.setScopeUserIds(new HashSet<>((List) userDataScopeMap.get("scopeUserIds")));
userDataScope.setScopeDeptIds(new HashSet<>((List) userDataScopeMap.get("scopeDeptIds")));
}
PigUser user = new PigUser(id, deptId, username, N_A, true, true, true, true, authorities, userDataScope);
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
}
return null;
......
package com.pig4cloud.pig.common.security.datascope;
import com.pig4cloud.pig.admin.api.entity.SysRole;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import java.util.List;
/**
* @author hccake
*/
public interface DataScopeProcessor {
/**
* 根据用户和角色信息,合并用户最终的数据权限
* @param user 用户
* @param roles 角色列表
* @return UserDataScope
*/
UserDataScope mergeScopeType(SysUser user, List<SysRole> roles);
}
package com.pig4cloud.pig.common.security.datascope;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pig.admin.api.entity.SysRole;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import com.pig4cloud.pig.admin.api.feign.RemoteDeptService;
import com.pig4cloud.pig.admin.api.feign.RemoteUserService;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.constant.enums.DataScopeTypeEnum;
import com.pig4cloud.pig.common.core.util.R;
import lombok.RequiredArgsConstructor;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author hccake
*/
@RequiredArgsConstructor
public class PigDataScopeProcessor implements DataScopeProcessor {
private final RemoteDeptService remoteDeptService;
private final RemoteUserService remoteUserService;
/**
* 合并角色的数据权限类型,排除相同的权限后,大的权限覆盖小的
* @param user 用户
* @param roles 角色列表
* @return List<Integer> 合并后的权限
*/
@Override
public UserDataScope mergeScopeType(SysUser user, List<SysRole> roles) {
UserDataScope userDataScope = new UserDataScope();
Set<Integer> scopeUserIds = userDataScope.getScopeUserIds();
Set<Integer> scopeDeptIds = userDataScope.getScopeDeptIds();
// 任何用户都应该可以看到自己的数据
Integer userId = user.getUserId();
scopeUserIds.add(userId);
if (CollectionUtil.isEmpty(roles)) {
return userDataScope;
}
// 根据角色的权限返回进行分组
Map<Integer, List<SysRole>> map = roles.stream().collect(Collectors.groupingBy(SysRole::getScopeType));
// 如果有全部权限,直接返回
if (map.containsKey(DataScopeTypeEnum.ALL.getType())) {
userDataScope.setAllScope(true);
return userDataScope;
}
// 如果有本级及子级,删除其包含的几类数据权限
boolean hasLevelChildLevel = map.containsKey(DataScopeTypeEnum.LEVEL_CHILD_LEVEL.getType());
if (hasLevelChildLevel) {
map.remove(DataScopeTypeEnum.SELF.getType());
map.remove(DataScopeTypeEnum.SELF_CHILD_LEVEL.getType());
map.remove(DataScopeTypeEnum.LEVEL.getType());
}
// 是否有本人及子级权限
boolean hasSelfChildLevel = map.containsKey(DataScopeTypeEnum.SELF_CHILD_LEVEL.getType());
// 是否有本级权限
boolean hasLevel = map.containsKey(DataScopeTypeEnum.LEVEL.getType());
if (hasSelfChildLevel || hasLevel) {
// 如果有本人及子级或者本级,都删除本人的数据权限
map.remove(DataScopeTypeEnum.SELF.getType());
// 如果同时拥有,则等于本级及子级权限
if (hasSelfChildLevel && hasLevel) {
map.remove(DataScopeTypeEnum.SELF_CHILD_LEVEL.getType());
map.remove(DataScopeTypeEnum.LEVEL.getType());
map.put(DataScopeTypeEnum.LEVEL_CHILD_LEVEL.getType(), new ArrayList<>());
}
}
// 这时如果仅仅只能看个人的,直接返回
if (map.size() == 1 && map.containsKey(DataScopeTypeEnum.SELF.getType())) {
userDataScope.setOnlySelf(true);
return userDataScope;
}
// 如果有 本级及子级 或者 本级,都把自己的 deptId 加进去
Integer deptId = user.getDeptId();
if (hasLevelChildLevel || hasLevel) {
scopeDeptIds.add(deptId);
}
// 如果有 本级及子级 或者 本人及子级,都把下级组织的 deptId 加进去
if (hasLevelChildLevel || hasSelfChildLevel) {
List<Integer> childDeptIdList = remoteDeptService.listChildDeptId(deptId, SecurityConstants.FROM_IN)
.getData();
if (CollectionUtil.isNotEmpty(childDeptIdList)) {
scopeDeptIds.addAll(childDeptIdList);
}
}
// 自定义部门
List<SysRole> sysRoles = map.get(DataScopeTypeEnum.CUSTOM.getType());
if (CollectionUtil.isNotEmpty(sysRoles)) {
Set<Integer> customDeptIds = sysRoles.stream().map(SysRole::getScopeResources).filter(Objects::nonNull)
.flatMap(x -> Arrays.stream(x.split(StrUtil.COMMA))).map(Integer::parseInt)
.collect(Collectors.toSet());
scopeDeptIds.addAll(customDeptIds);
}
// 把部门对应的用户id都放入集合中
if (CollectionUtil.isNotEmpty(scopeDeptIds)) {
R<List<Integer>> r = remoteUserService.listUserIdByDeptIds(scopeDeptIds, SecurityConstants.FROM_IN);
List<Integer> userIds = r.getData();
if (CollectionUtil.isNotEmpty(userIds)) {
scopeUserIds.addAll(userIds);
}
}
return userDataScope;
}
}
package com.pig4cloud.pig.common.security.datascope;
import lombok.Data;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* @author hccake
*/
@Data
public class UserDataScope implements Serializable {
/**
* 是否是全部数据权限
*/
private boolean allScope = false;
/**
* 是否仅能看自己
*/
private boolean onlySelf = false;
/**
* 数据权限范围,用户所能查看的用户id 集合
*/
private Set<Integer> scopeUserIds = new HashSet<>();
/**
* 数据权限范围,用户所能查看的部门id 集合
*/
private Set<Integer> scopeDeptIds = new HashSet<>();
}
......@@ -16,6 +16,7 @@
package com.pig4cloud.pig.common.security.service;
import com.pig4cloud.pig.common.security.datascope.UserDataScope;
import lombok.Getter;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.GrantedAuthority;
......@@ -33,13 +34,19 @@ public class PigUser extends User {
* 用户ID
*/
@Getter
private Integer id;
private final Integer id;
/**
* 部门ID
*/
@Getter
private Integer deptId;
private final Integer deptId;
/**
* 用户数据权限信息
*/
@Getter
private final UserDataScope userDataScope;
/**
* Construct the <code>User</code> with the details required by
......@@ -62,10 +69,11 @@ public class PigUser extends User {
*/
public PigUser(Integer id, Integer deptId, String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Collection<? extends GrantedAuthority> authorities, UserDataScope userDataScope) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.id = id;
this.deptId = deptId;
this.userDataScope = userDataScope;
}
}
......@@ -25,6 +25,8 @@ import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.constant.CommonConstants;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.security.datascope.DataScopeProcessor;
import com.pig4cloud.pig.common.security.datascope.UserDataScope;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
......@@ -45,7 +47,7 @@ import java.util.Set;
/**
* 用户详细信息
*
* @author lengleng
* @author lengleng hccake
*/
@Slf4j
@Service
......@@ -56,6 +58,8 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
private final CacheManager cacheManager;
private final DataScopeProcessor dataScopeProcessor;
/**
* 用户密码登录
* @param username 用户名
......@@ -80,7 +84,7 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
/**
* 构建userdetails
* @param result 用户信息
* @return
* @return UserDetails
*/
private UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
......@@ -100,10 +104,14 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
// 数据权限填充
UserDataScope userDataScope = dataScopeProcessor.mergeScopeType(user, info.getRoleList());
// 构造security用户
return new PigUser(user.getUserId(), user.getDeptId(), user.getUsername(),
SecurityConstants.BCRYPT + user.getPassword(),
StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL), true, true, true, authorities);
StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL), true, true, true, authorities,
userDataScope);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl,\
com.pig4cloud.pig.common.security.component.PigSecurityInnerAspect
com.pig4cloud.pig.common.security.component.PigSecurityInnerAspect,\
com.pig4cloud.pig.common.security.datascope.PigDataScopeProcessor
......@@ -40,5 +40,6 @@
<module>pig-common-feign</module>
<module>pig-common-swagger</module>
<module>pig-common-test</module>
</modules>
<module>pig-common-datascope</module>
</modules>
</project>
......@@ -16,10 +16,14 @@
package com.pig4cloud.pig.admin.api.dto;
import com.pig4cloud.pig.admin.api.entity.SysMenu;
import com.pig4cloud.pig.admin.api.entity.SysRole;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* @author lengleng
......@@ -46,4 +50,9 @@ public class UserInfo implements Serializable {
*/
private Integer[] roles;
/**
* 角色集合
*/
private List<SysRole> roleList;
}
......@@ -25,6 +25,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* <p>
......@@ -56,6 +57,13 @@ public class SysRole extends BaseEntity {
@ApiModelProperty(value = "角色描述")
private String roleDesc;
@NotNull(message = "数据范围类型 不能为null")
@ApiModelProperty(value = "数据范围类型")
private Integer scopeType;
@ApiModelProperty(value = "数据范围资源")
private String scopeResources;
/**
* 删除标识(0-正常,1-删除)
*/
......
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.pig4cloud.pig.admin.api.feign;
import com.pig4cloud.pig.admin.api.feign.factory.RemoteDeptServiceFallbackFactory;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.constant.ServiceNameConstants;
import com.pig4cloud.pig.common.core.util.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
/**
* @author hccake
*/
@FeignClient(contextId = "remoteDeptService", value = ServiceNameConstants.UMPS_SERVICE,
fallbackFactory = RemoteDeptServiceFallbackFactory.class)
public interface RemoteDeptService {
/**
* 查收子级id列表
* @return 返回子级id列表
*/
@GetMapping("/dept/child-id/{deptId}")
R<List<Integer>> listChildDeptId(@PathVariable("deptId") Integer deptId,
@RequestHeader(SecurityConstants.FROM) String from);
}
......@@ -25,6 +25,10 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Set;
/**
* @author lengleng
......@@ -51,4 +55,14 @@ public interface RemoteUserService {
@GetMapping("/social/info/{inStr}")
R<UserInfo> social(@PathVariable("inStr") String inStr);
/**
* 根据部门id,查询对应的用户 id 集合
* @param deptIds 部门id 集合
* @param from 调用标志
* @return 用户 id 集合
*/
@GetMapping("/user/ids")
R<List<Integer>> listUserIdByDeptIds(@RequestParam("deptIds") Set<Integer> deptIds,
@RequestHeader(SecurityConstants.FROM) String from);
}
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.pig4cloud.pig.admin.api.feign.factory;
import com.pig4cloud.pig.admin.api.feign.RemoteDeptService;
import com.pig4cloud.pig.admin.api.feign.fallback.RemoteDeptServiceFallbackImpl;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @author hccake
*/
@Component
public class RemoteDeptServiceFallbackFactory implements FallbackFactory<RemoteDeptService> {
@Override
public RemoteDeptService create(Throwable throwable) {
RemoteDeptServiceFallbackImpl remoteDeptServiceFallback = new RemoteDeptServiceFallbackImpl();
remoteDeptServiceFallback.setCause(throwable);
return remoteDeptServiceFallback;
}
}
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.pig4cloud.pig.admin.api.feign.fallback;
import com.pig4cloud.pig.admin.api.dto.UserInfo;
import com.pig4cloud.pig.admin.api.feign.RemoteDeptService;
import com.pig4cloud.pig.admin.api.feign.RemoteUserService;
import com.pig4cloud.pig.common.core.util.R;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author hccake
*/
@Slf4j
@Component
public class RemoteDeptServiceFallbackImpl implements RemoteDeptService {
@Setter
private Throwable cause;
@Override
public R<List<Integer>> listChildDeptId(Integer deptId, String from) {
log.error("[listChildDeptId] feign 查询子级部门id列表失败", cause);
return null;
}
}
......@@ -23,6 +23,9 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
/**
* @author lengleng
* @date 2019/2/1
......@@ -57,4 +60,10 @@ public class RemoteUserServiceFallbackImpl implements RemoteUserService {
return null;
}
@Override
public R<List<Integer>> listUserIdByDeptIds(Set<Integer> deptIds, String from) {
log.error("feign 根据部门ids查询用户Id集合失败:{}", deptIds, cause);
return null;
}
}
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.pig4cloud.pig.admin.api.vo;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import lombok.Data;
import java.io.Serializable;
/**
* @author lengleng
* @date 2019/2/1
* <p>
* commit('SET_ROLES', data) commit('SET_NAME', data) commit('SET_AVATAR', data)
* commit('SET_INTRODUCTION', data) commit('SET_PERMISSIONS', data)
*/
@Data
public class UserInfoVO implements Serializable {
/**
* 用户基本信息
*/
private SysUser sysUser;
/**
* 权限标识集合
*/
private String[] permissions;
/**
* 角色集合
*/
private Integer[] roles;
}
......@@ -2,6 +2,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pig.admin.api.feign.fallback.RemoteUserServiceFallbackImpl,\
com.pig4cloud.pig.admin.api.feign.fallback.RemoteLogServiceFallbackImpl,\
com.pig4cloud.pig.admin.api.feign.fallback.RemoteTokenServiceFallbackImpl,\
com.pig4cloud.pig.admin.api.feign.fallback.RemoteDeptServiceFallbackImpl,\
com.pig4cloud.pig.admin.api.feign.factory.RemoteUserServiceFallbackFactory,\
com.pig4cloud.pig.admin.api.feign.factory.RemoteLogServiceFallbackFactory,\
com.pig4cloud.pig.admin.api.feign.factory.RemoteTokenServiceFallbackFactory
com.pig4cloud.pig.admin.api.feign.factory.RemoteTokenServiceFallbackFactory,\
com.pig4cloud.pig.admin.api.feign.factory.RemoteDeptServiceFallbackFactory
......@@ -60,6 +60,11 @@
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-mybatis</artifactId>
</dependency>
<!--数据权限 模块-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-datascope</artifactId>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
......
......@@ -20,6 +20,7 @@ import com.pig4cloud.pig.admin.api.entity.SysDept;
import com.pig4cloud.pig.admin.service.SysDeptService;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.log.annotation.SysLog;
import com.pig4cloud.pig.common.security.annotation.Inner;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
......@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
/**
* <p>
......@@ -121,4 +123,14 @@ public class DeptController {
return R.ok(sysDeptService.getOne(new QueryWrapper<>(condition)));
}
/**
* 查收子级id列表
* @return 返回子级id列表
*/
@Inner
@GetMapping(value = "/child-id/{deptId}")
public R<List<Integer>> listChildDeptId(@PathVariable Integer deptId) {
return R.ok(sysDeptService.listChildDeptId(deptId));
}
}
......@@ -20,8 +20,10 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pig4cloud.pig.admin.api.dto.UserDTO;
import com.pig4cloud.pig.admin.api.dto.UserInfo;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import com.pig4cloud.pig.admin.api.vo.UserExcelVO;
import com.pig4cloud.pig.admin.api.vo.UserInfoVO;
import com.pig4cloud.pig.admin.service.SysUserService;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.log.annotation.SysLog;
......@@ -33,10 +35,19 @@ import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
import java.util.Set;
/**
* @author lengleng
......@@ -61,7 +72,12 @@ public class UserController {
if (user == null) {
return R.failed("获取当前用户信息失败");
}
return R.ok(userService.getUserInfo(user));
UserInfo userInfo = userService.getUserInfo(user);
UserInfoVO vo = new UserInfoVO();
vo.setSysUser(userInfo.getSysUser());
vo.setRoles(userInfo.getRoles());
vo.setPermissions(userInfo.getPermissions());
return R.ok(vo);
}
/**
......@@ -78,6 +94,17 @@ public class UserController {
return R.ok(userService.getUserInfo(user));
}
/**
* 根据部门id,查询对应的用户 id 集合
* @param deptIds 部门id 集合
* @return 用户 id 集合
*/
@Inner
@GetMapping("/ids")
public R<List<Integer>> listUserIdByDeptIds(@RequestParam("deptIds") Set<Integer> deptIds) {
return R.ok(userService.listUserIdByDeptIds(deptIds));
}
/**
* 通过ID查询用户信息
* @param id ID
......
package com.pig4cloud.pig.admin.datascope;
import cn.hutool.core.collection.CollectionUtil;
import com.pig4cloud.pig.common.security.datascope.UserDataScope;
import com.pig4cloud.pig.common.security.service.PigUser;
import com.pig4cloud.pig.common.security.util.SecurityUtils;
import com.pigcloud.pig.common.datascope.DataScope;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
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 org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* 数据权限控制,要求表至少有个 user_id 字段
*
* @author hccake
*/
@Component
public class PigDataScope implements DataScope {
private static final String USER_ID = "user_id";
private static final String DEPT_ID = "dept_id";
/**
* 拥有 dept_id 字段的表名集合
*/
private static final Set<String> DEPT_ID_TABLE_NAMES = CollectionUtil.newHashSet("sys_user");
/**
* 仅拥有 user_id 字段的表名集合
*/
private static final Set<String> USER_ID_TABLE_NAMES = CollectionUtil.newHashSet();
private final Set<String> tableNames;
public PigDataScope() {
Set<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set.addAll(DEPT_ID_TABLE_NAMES);
set.addAll(USER_ID_TABLE_NAMES);
this.tableNames = Collections.unmodifiableSet(set);
}
@Override
public String getResource() {
return "userData";
}
@Override
public Collection<String> getTableNames() {
return this.tableNames;
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// 获取当前登录用户
PigUser user = SecurityUtils.getUser();
if (user == null) {
return null;
}
UserDataScope userDataScope = user.getUserDataScope();
// 如果数据权限是全部,直接放行
if (userDataScope.isAllScope()) {
return null;
}
// 如果数据权限是仅自己
if (userDataScope.isOnlySelf()) {
// 数据权限规则,where user_id = xx
return userIdEqualsToExpression(tableAlias, user.getId());
}
// 如果当前表有部门字段,则优先使用部门字段控制范围
if (DEPT_ID_TABLE_NAMES.contains(tableName)) {
// 数据权限规则,where (user_id =xx or dept_id in ("x","y"))
EqualsTo equalsTo = userIdEqualsToExpression(tableAlias, user.getId());
Expression inExpression = getInExpression(tableAlias, DEPT_ID, userDataScope.getScopeDeptIds());
// 这里一定要加括号,否则如果有其他查询条件,or 会出问题
return new Parenthesis(new OrExpression(equalsTo, inExpression));
}
else {
// 数据权限规则,where user_id in ("x","y")
return getInExpression(tableAlias, USER_ID, userDataScope.getScopeUserIds());
}
}
private EqualsTo userIdEqualsToExpression(Alias tableAlias, Integer userId) {
Column column = new Column(tableAlias == null ? USER_ID : tableAlias.getName() + "." + USER_ID);
return new EqualsTo(column, new LongValue(userId));
}
private Expression getInExpression(Alias tableAlias, String columnName, Set<Integer> scopeUserIds) {
Column column = new Column(tableAlias == null ? columnName : tableAlias.getName() + "." + columnName);
ExpressionList expressionList = new ExpressionList();
List<Expression> list = scopeUserIds.stream().map(LongValue::new).collect(Collectors.toList());
expressionList.setExpressions(list);
return new InExpression(column, expressionList);
}
}
......@@ -65,4 +65,11 @@ public interface SysDeptService extends IService<SysDept> {
*/
Boolean updateDeptById(SysDept sysDept);
/**
* 查找指定部门的子部门id列表
* @param deptId 部门id
* @return List<Integer>
*/
List<Integer> listChildDeptId(Integer deptId);
}
......@@ -28,6 +28,7 @@ import com.pig4cloud.pig.common.core.util.R;
import org.springframework.validation.BindingResult;
import java.util.List;
import java.util.Set;
/**
* @author lengleng
......@@ -107,4 +108,11 @@ public interface SysUserService extends IService<SysUser> {
*/
R importUser(List<UserExcelVO> excelVOList, BindingResult bindingResult);
/**
* 根据部门 id 列表查询对应的用户 id 集合
* @param deptIds 部门 id 列表
* @return userIdList
*/
List<Integer> listUserIdByDeptIds(Set<Integer> deptIds);
}
......@@ -33,6 +33,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
......@@ -108,6 +109,16 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
return Boolean.TRUE;
}
@Override
public List<Integer> listChildDeptId(Integer deptId) {
List<SysDeptRelation> deptRelations = sysDeptRelationService.list(Wrappers.<SysDeptRelation>lambdaQuery()
.eq(SysDeptRelation::getAncestor, deptId).ne(SysDeptRelation::getDescendant, deptId));
if (CollUtil.isNotEmpty(deptRelations)) {
return deptRelations.stream().map(SysDeptRelation::getDescendant).collect(Collectors.toList());
}
return new ArrayList<>();
}
/**
* 查询全部部门树
* @return 树
......
......@@ -25,7 +25,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pig.admin.api.dto.UserDTO;
import com.pig4cloud.pig.admin.api.dto.UserInfo;
import com.pig4cloud.pig.admin.api.entity.*;
import com.pig4cloud.pig.admin.api.entity.SysDept;
import com.pig4cloud.pig.admin.api.entity.SysMenu;
import com.pig4cloud.pig.admin.api.entity.SysRole;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import com.pig4cloud.pig.admin.api.entity.SysUserRole;
import com.pig4cloud.pig.admin.api.vo.UserExcelVO;
import com.pig4cloud.pig.admin.api.vo.UserVO;
import com.pig4cloud.pig.admin.mapper.SysDeptMapper;
......@@ -107,9 +111,11 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public UserInfo getUserInfo(SysUser sysUser) {
UserInfo userInfo = new UserInfo();
userInfo.setSysUser(sysUser);
// 设置角色列表
List<SysRole> roleList = sysRoleMapper.listRolesByUserId(sysUser.getUserId());
userInfo.setRoleList(roleList);
// 设置角色列表 (ID)
List<Integer> roleIds = sysRoleMapper.listRolesByUserId(sysUser.getUserId()).stream().map(SysRole::getRoleId)
.collect(Collectors.toList());
List<Integer> roleIds = roleList.stream().map(SysRole::getRoleId).collect(Collectors.toList());
userInfo.setRoles(ArrayUtil.toArray(roleIds, Integer.class));
// 设置权限列表(menu.permission)
Set<String> permissions = sysMenuService.findMenuByRoleId(CollUtil.join(roleIds, StrUtil.COMMA)).stream()
......@@ -117,6 +123,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
.filter(m -> StrUtil.isNotBlank(m.getPermission())).map(SysMenu::getPermission)
.collect(Collectors.toSet());
userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
return userInfo;
}
......@@ -295,6 +302,13 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return R.ok();
}
@Override
public List<Integer> listUserIdByDeptIds(Set<Integer> deptIds) {
return this.listObjs(
Wrappers.lambdaQuery(SysUser.class).select(SysUser::getUserId).in(SysUser::getDeptId, deptIds),
Integer.class::cast);
}
/**
* 插入excel User
*/
......
......@@ -47,12 +47,6 @@
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- mybatis-starter:mybatis + mybatis-spring + hikari(default) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册