mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 04:39:00 +08:00
refactor(pigx-common-data): 解耦 pigx-common-data 对 pigx-common-security 的依赖
This commit is contained in:
1 parent
5ad4dfb711
commit
0fca2924d4
12 files changed
+275
-340
No files matched your search
+2
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -49,12 +49,10 @@ public class RedisAutoCacheManager extends RedisCacheManager {
|
||||
private static final int CACHE_LENGTH = 2;
|
||||
|
||||
public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
|
||||
boolean allowInFlightCacheCreation,
|
||||
Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
|
||||
boolean allowInFlightCacheCreation, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
|
||||
super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheConfigurations);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
|
||||
if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
|
||||
|
||||
+15
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -17,16 +17,14 @@
|
||||
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.common.core.jackson.PigxJavaTimeModule;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheManagerCustomizers;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -49,9 +47,10 @@ import java.util.Objects;
|
||||
* @author L.cm
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter({RedisAutoConfiguration.class})
|
||||
@AutoConfigureAfter({DataRedisAutoConfiguration.class})
|
||||
@ConditionalOnBean({RedisConnectionFactory.class})
|
||||
@EnableConfigurationProperties(CacheProperties.class)
|
||||
@SuppressWarnings("removal")
|
||||
public class RedisCacheAutoConfiguration {
|
||||
|
||||
private final CacheProperties cacheProperties;
|
||||
@@ -70,11 +69,12 @@ public class RedisCacheAutoConfiguration {
|
||||
|
||||
@Primary
|
||||
@Bean("cacheResolver")
|
||||
public CacheManager redisCacheManager(ObjectProvider<RedisConnectionFactory> connectionFactoryObjectProvider) {
|
||||
public CacheManager redisCacheManager(ObjectProvider<RedisConnectionFactory> connectionFactoryObjectProvider,
|
||||
ObjectMapper objectMapper) {
|
||||
RedisConnectionFactory connectionFactory = connectionFactoryObjectProvider.getIfAvailable();
|
||||
Objects.requireNonNull(connectionFactory, "Bean RedisConnectionFactory is null.");
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
RedisCacheConfiguration cacheConfiguration = this.determineConfiguration();
|
||||
RedisCacheConfiguration cacheConfiguration = this.determineConfiguration(objectMapper);
|
||||
List<String> cacheNames = this.cacheProperties.getCacheNames();
|
||||
Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
|
||||
if (!cacheNames.isEmpty()) {
|
||||
@@ -91,16 +91,18 @@ public class RedisCacheAutoConfiguration {
|
||||
return this.customizerInvoker.customize(cacheManager);
|
||||
}
|
||||
|
||||
private RedisCacheConfiguration determineConfiguration() {
|
||||
private RedisCacheConfiguration determineConfiguration(ObjectMapper objectMapper) {
|
||||
if (this.redisCacheConfiguration != null) {
|
||||
return this.redisCacheConfiguration;
|
||||
} else {
|
||||
CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
|
||||
RedisSerializer<Object> jsonRedisSerializer = RedisSerializer.json();
|
||||
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(jsonRedisSerializer, "mapper");
|
||||
objectMapper.registerModules(new PigxJavaTimeModule());
|
||||
ObjectMapper redisObjectMapper = objectMapper.copy();
|
||||
RedisSerializer<Object> jsonRedisSerializer = org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.builder()
|
||||
.objectMapper(redisObjectMapper)
|
||||
.defaultTyping(true)
|
||||
.build();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||
.fromSerializer(jsonRedisSerializer));
|
||||
if (redisProperties.getTimeToLive() != null) {
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -18,9 +18,9 @@
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheManagerCustomizer;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheManagerCustomizers;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.pig4cloud.pigx.common.data.cache;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -33,8 +34,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
*/
|
||||
@EnableCaching
|
||||
@Configuration
|
||||
@AutoConfigureBefore(name = { "org.redisson.spring.starter.RedissonAutoConfigurationV2",
|
||||
"org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration" })
|
||||
@AutoConfigureBefore(value = DataRedisAutoConfiguration.class, name = "org.redisson.spring.starter.RedissonAutoConfigurationV2")
|
||||
public class RedisTemplateConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
+26
-156
@@ -4,18 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.FromItem;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
@@ -26,17 +15,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据权限内部拦截器
|
||||
* <p>
|
||||
* 参照 MyBatis Plus {@code DataPermissionInterceptor} 的实现思路,
|
||||
* 通过 JSqlParser 解析 SQL 为 AST,使用 {@code PlainSelect.setWhere()} 直接在
|
||||
* WHERE 子句上追加数据权限条件,避免子查询包装导致索引失效。
|
||||
* 对于 UNION 等复杂 SQL 或解析失败的情况,回退到子查询包装方式保证兼容性。
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020/11/29
|
||||
*/
|
||||
@Slf4j
|
||||
public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
|
||||
@Setter
|
||||
@@ -72,148 +53,37 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Statement statement = CCJSqlParserUtil.parse(originalSql);
|
||||
if (!(statement instanceof Select selectStatement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UNION 等复杂语句无法直接操作 WHERE,回退到子查询包装
|
||||
if (!(selectStatement.getSelectBody() instanceof PlainSelect plainSelect)) {
|
||||
mpBs.sql(buildSubQuerySql(originalSql, dataScope));
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建数据权限条件表达式
|
||||
Expression scopeExpression = buildScopeExpression(plainSelect, dataScope);
|
||||
if (scopeExpression == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 与原有 WHERE 条件合并(数据权限条件 AND 原条件)
|
||||
Expression existingWhere = plainSelect.getWhere();
|
||||
if (existingWhere != null) {
|
||||
plainSelect.setWhere(new AndExpression(scopeExpression, existingWhere));
|
||||
} else {
|
||||
plainSelect.setWhere(scopeExpression);
|
||||
}
|
||||
|
||||
mpBs.sql(selectStatement.toString());
|
||||
} catch (JSQLParserException e) {
|
||||
log.warn("数据权限 SQL 解析失败,回退到子查询包装: {}", originalSql, e);
|
||||
mpBs.sql(buildSubQuerySql(originalSql, dataScope));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 DataScope 配置构建数据权限条件表达式
|
||||
* <p>
|
||||
* 使用 {@code CCJSqlParserUtil.parseCondExpression()} 解析条件字符串为 Expression 对象,
|
||||
* 与 MyBatis Plus DataPermissionInterceptor 保持一致的实现方式。
|
||||
*
|
||||
* @param plainSelect 解析后的 PlainSelect 对象
|
||||
* @param dataScope 数据权限参数
|
||||
* @return 数据权限条件表达式,无需处理时返回 null
|
||||
*/
|
||||
private Expression buildScopeExpression(PlainSelect plainSelect, DataScope dataScope)
|
||||
throws JSQLParserException {
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
String username = dataScope.getUsername();
|
||||
|
||||
// 获取表引用(优先别名,其次表名),用于限定列名
|
||||
String tableRef = getTableReference(plainSelect.getFromItem());
|
||||
String prefix = StrUtil.isNotBlank(tableRef) ? tableRef + "." : "";
|
||||
|
||||
// 1. 无数据权限限制,返回 0 条数据
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) {
|
||||
return CCJSqlParserUtil.parseCondExpression("1 = 2");
|
||||
// 1.无数据权限限制,则直接返回 0 条数据
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) {
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
|
||||
dataScope.getFunc().getType(), originalSql);
|
||||
}
|
||||
|
||||
// SQL 注入检查
|
||||
if (StrUtil.isNotBlank(username) && SqlInjectionUtils.check(username)) {
|
||||
return CCJSqlParserUtil.parseCondExpression("1 = 2");
|
||||
}
|
||||
|
||||
// 2. 本人权限 + 部门权限:加括号保证与原有条件组合时的优先级
|
||||
if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) {
|
||||
// 2.如果为本人权限 + 部门权限控制
|
||||
else if (StrUtil.isNotBlank(dataScope.getUsername()) && CollUtil.isNotEmpty(deptIds)) {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
// 使用配置的组合模式(AND/OR),默认OR保持向后兼容
|
||||
String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword()
|
||||
: DataScopeLogicEnum.OR.getKeyword();
|
||||
String condSql = String.format("(%s%s = '%s' %s %s%s IN (%s))", prefix, dataScope.getScopeUserName(),
|
||||
username, logicKeyword, prefix, dataScope.getScopeDeptName(), join);
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
originalSql = String.format(
|
||||
"SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s)",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername(),
|
||||
logicKeyword, dataScope.getScopeDeptName(), join);
|
||||
}
|
||||
|
||||
// 3. 仅本人权限
|
||||
if (StrUtil.isNotBlank(username)) {
|
||||
String condSql = String.format("%s%s = '%s'", prefix, dataScope.getScopeUserName(), username);
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
// 3. 如果为本人
|
||||
else if (StrUtil.isNotBlank(dataScope.getUsername())) {
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());
|
||||
}
|
||||
|
||||
// 4. 仅部门权限
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
String condSql = String.format("%s%s IN (%s)", prefix, dataScope.getScopeDeptName(), join);
|
||||
return CCJSqlParserUtil.parseCondExpression(condSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表引用名称(优先别名,其次表名)
|
||||
*
|
||||
* @param fromItem FROM 子句项
|
||||
* @return 表引用名称,无法获取时返回 null
|
||||
*/
|
||||
private String getTableReference(FromItem fromItem) {
|
||||
if (fromItem == null) {
|
||||
return null;
|
||||
}
|
||||
if (fromItem.getAlias() != null) {
|
||||
return fromItem.getAlias().getName();
|
||||
}
|
||||
if (fromItem instanceof Table table) {
|
||||
return table.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退方案:子查询包装(用于 UNION 等复杂 SQL 或解析失败时)
|
||||
*/
|
||||
private String buildSubQuerySql(String originalSql, DataScope dataScope) {
|
||||
List<Long> deptIds = dataScope.getDeptList();
|
||||
String username = dataScope.getUsername();
|
||||
String selectType = dataScope.getFunc().getType();
|
||||
|
||||
// 无权限
|
||||
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) {
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2", selectType, originalSql);
|
||||
}
|
||||
|
||||
// SQL 注入检查
|
||||
if (StrUtil.isNotBlank(username) && SqlInjectionUtils.check(username)) {
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2", selectType, originalSql);
|
||||
}
|
||||
|
||||
// 本人 + 部门
|
||||
if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) {
|
||||
// 4.部门权限控制
|
||||
else {
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword()
|
||||
: DataScopeLogicEnum.OR.getKeyword();
|
||||
return String.format(
|
||||
"SELECT %s FROM (%s) temp_data_scope WHERE (temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s))",
|
||||
selectType, originalSql, dataScope.getScopeUserName(), username, logicKeyword,
|
||||
dataScope.getScopeDeptName(), join);
|
||||
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
|
||||
dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);
|
||||
}
|
||||
|
||||
// 仅本人
|
||||
if (StrUtil.isNotBlank(username)) {
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'", selectType,
|
||||
originalSql, dataScope.getScopeUserName(), username);
|
||||
}
|
||||
|
||||
// 仅部门
|
||||
String join = CollectionUtil.join(deptIds, ",");
|
||||
return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)", selectType,
|
||||
originalSql, dataScope.getScopeDeptName(), join);
|
||||
mpBs.sql(originalSql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,11 +98,11 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor {
|
||||
} else if (parameterObj instanceof Map) {
|
||||
for (Object val : ((Map<?, ?>) parameterObj).values()) {
|
||||
if (val instanceof DataScope) {
|
||||
return (DataScope) val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (DataScope) val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-29
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -22,26 +22,16 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysDept;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysRole;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteDataScopeService;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.enums.UserTypeEnum;
|
||||
import com.pig4cloud.pigx.common.core.context.UserContext;
|
||||
import com.pig4cloud.pigx.common.core.context.UserContextHolder;
|
||||
import com.pig4cloud.pigx.common.core.util.RetOps;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-09-07
|
||||
* <p>
|
||||
* 默认data scope 判断处理器
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-09-07
|
||||
@@ -53,12 +43,13 @@ public class PigxDefaultDataScopeHandle implements DataScopeHandle {
|
||||
|
||||
private final RemoteDataScopeService dataScopeService;
|
||||
|
||||
private final UserContextHolder userContextHolder;
|
||||
|
||||
@Override
|
||||
public Boolean calcScope(DataScope dataScope) {
|
||||
// 对于TOC客户端,不进行数据权限校验
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
if (UserTypeEnum.TOC.getStatus().equals(user.getUserType())) {
|
||||
return true;
|
||||
UserContext user = userContextHolder.get();
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 业务代码里的规则,覆盖计算规则
|
||||
@@ -66,19 +57,13 @@ public class PigxDefaultDataScopeHandle implements DataScopeHandle {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取用户角色ID列表
|
||||
List<String> roleIdList = user.getAuthorities()
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.filter(authority -> authority.startsWith(SecurityConstants.ROLE))
|
||||
.map(authority -> authority.split(StrUtil.UNDERLINE)[1])
|
||||
.toList();
|
||||
List<Long> roleIdList = user.getRoleIds();
|
||||
if (CollUtil.isEmpty(roleIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取角色列表
|
||||
List<SysRole> roleList = RetOps.of(dataScopeService.getRoleList(roleIdList))
|
||||
List<SysRole> roleList = RetOps.of(dataScopeService.getRoleList(roleIdList.stream().map(String::valueOf).toList()))
|
||||
.getData()
|
||||
.orElseGet(Collections::emptyList);
|
||||
if (CollUtil.isEmpty(roleList)) {
|
||||
@@ -92,7 +77,7 @@ public class PigxDefaultDataScopeHandle implements DataScopeHandle {
|
||||
/**
|
||||
* 处理数据权限
|
||||
*/
|
||||
private boolean processDataScope(PigxUser user, DataScope dataScope, List<SysRole> roleList) {
|
||||
private boolean processDataScope(UserContext user, DataScope dataScope, List<SysRole> roleList) {
|
||||
List<Long> deptList = dataScope.getDeptList();
|
||||
|
||||
for (SysRole role : roleList) {
|
||||
@@ -134,7 +119,7 @@ public class PigxDefaultDataScopeHandle implements DataScopeHandle {
|
||||
/**
|
||||
* 处理本级及下级数据权限
|
||||
*/
|
||||
private void handleOwnChildLevelScope(PigxUser user, List<Long> deptList) {
|
||||
private void handleOwnChildLevelScope(UserContext user, List<Long> deptList) {
|
||||
List<Long> descendantDeptIds = RetOps.of(dataScopeService.getDescendantList(user.getDeptId()))
|
||||
.getData()
|
||||
.orElseGet(Collections::emptyList)
|
||||
@@ -147,14 +132,14 @@ public class PigxDefaultDataScopeHandle implements DataScopeHandle {
|
||||
/**
|
||||
* 处理本级数据权限
|
||||
*/
|
||||
private void handleOwnLevelScope(PigxUser user, List<Long> deptList) {
|
||||
private void handleOwnLevelScope(UserContext user, List<Long> deptList) {
|
||||
deptList.add(user.getDeptId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人数据权限
|
||||
*/
|
||||
private void handleSelfLevelScope(PigxUser user, DataScope dataScope) {
|
||||
private void handleSelfLevelScope(UserContext user, DataScope dataScope) {
|
||||
dataScope.setUsername(user.getUsername());
|
||||
}
|
||||
|
||||
|
||||
+23
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -23,6 +23,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInt
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteDataScopeService;
|
||||
import com.pig4cloud.pigx.common.core.context.UserContextHolder;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeInnerInterceptor;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeInterceptor;
|
||||
import com.pig4cloud.pigx.common.data.datascope.DataScopeSqlInjector;
|
||||
@@ -30,15 +31,15 @@ import com.pig4cloud.pigx.common.data.datascope.PigxDefaultDataScopeHandle;
|
||||
import com.pig4cloud.pigx.common.data.resolver.SqlFilterArgumentResolver;
|
||||
import com.pig4cloud.pigx.common.data.tenant.PigxTenantConfigProperties;
|
||||
import com.pig4cloud.pigx.common.data.tenant.PigxTenantHandler;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
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.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
@@ -53,6 +54,7 @@ import java.util.Properties;
|
||||
* @author lengleng
|
||||
* @date 2020-02-08
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnBean(DataSource.class)
|
||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||
@@ -71,17 +73,26 @@ public class MybatisPlusConfiguration implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* mybatis plus 拦截器配置
|
||||
* <p>
|
||||
* 数据权限拦截器 {@link DataScopeInterceptor} 改为可选注入:
|
||||
* 未提供 {@link com.pig4cloud.pigx.common.core.context.UserContextHolder} 的场景下不装配,
|
||||
* 同时记录 WARN 日志,租户/分页/乐观锁等其它能力仍正常工作。
|
||||
*
|
||||
* @return PigxDefaultDatascopeHandle
|
||||
* @return MybatisPlusInterceptor
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineInnerInterceptor tenantLineInnerInterceptor,
|
||||
DataScopeInterceptor dataScopeInterceptor) {
|
||||
ObjectProvider<DataScopeInterceptor> dataScopeInterceptorProvider) {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 注入多租户支持
|
||||
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
|
||||
// 数据权限
|
||||
interceptor.addInnerInterceptor(dataScopeInterceptor);
|
||||
// 数据权限(可选)
|
||||
DataScopeInterceptor dataScopeInterceptor = dataScopeInterceptorProvider.getIfAvailable();
|
||||
if (dataScopeInterceptor != null) {
|
||||
interceptor.addInnerInterceptor(dataScopeInterceptor);
|
||||
} else {
|
||||
log.warn("No UserContextHolder bean found, DataScopeInterceptor disabled.");
|
||||
}
|
||||
// 分页支持
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
paginationInnerInterceptor.setMaxLimit(1000L);
|
||||
@@ -112,10 +123,11 @@ public class MybatisPlusConfiguration implements WebMvcConfigurer {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(PigxUser.class)
|
||||
public DataScopeInterceptor dataScopeInterceptor(RemoteDataScopeService dataScopeService) {
|
||||
@ConditionalOnBean(UserContextHolder.class)
|
||||
public DataScopeInterceptor dataScopeInterceptor(RemoteDataScopeService dataScopeService,
|
||||
UserContextHolder userContextHolder) {
|
||||
DataScopeInnerInterceptor dataScopeInnerInterceptor = new DataScopeInnerInterceptor();
|
||||
dataScopeInnerInterceptor.setDataScopeHandle(new PigxDefaultDataScopeHandle(dataScopeService));
|
||||
dataScopeInnerInterceptor.setDataScopeHandle(new PigxDefaultDataScopeHandle(dataScopeService, userContextHolder));
|
||||
return dataScopeInnerInterceptor;
|
||||
}
|
||||
|
||||
|
||||
+56
-10
@@ -3,11 +3,16 @@ package com.pig4cloud.pigx.common.data.resolver;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteParamService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
@@ -20,17 +25,42 @@ public class ParamResolver {
|
||||
|
||||
/**
|
||||
* 根据多个key 查询value 配置 结果使用hutool 的maputil 进行包装处理 MapUtil.getBool(result,key)
|
||||
* @param key key
|
||||
* @param keys key
|
||||
* @return Map<String,Object>
|
||||
*/
|
||||
public Map<String, Object> getMap(String... key) {
|
||||
public Map<String, Object> getMap(String... keys) {
|
||||
// 校验入参是否合法
|
||||
if (Objects.isNull(key)) {
|
||||
if (keys == null) {
|
||||
throw new IllegalArgumentException("参数不合法");
|
||||
}
|
||||
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
return remoteParamService.getByKeys(key).getData();
|
||||
Cache cache = paramsCache();
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
List<String> missedKeys = new ArrayList<>();
|
||||
|
||||
for (String k : keys) {
|
||||
Cache.ValueWrapper wrapper = (cache != null) ? cache.get(k) : null;
|
||||
if (wrapper != null) {
|
||||
result.put(k, wrapper.get());
|
||||
}
|
||||
else {
|
||||
missedKeys.add(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (!missedKeys.isEmpty()) {
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
Map<String, Object> fetched = remoteParamService.getByKeys(missedKeys.toArray(new String[0])).getData();
|
||||
if (fetched != null) {
|
||||
result.putAll(fetched);
|
||||
if (cache != null) {
|
||||
fetched.forEach(cache::put);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,19 +89,35 @@ public class ParamResolver {
|
||||
throw new IllegalArgumentException("参数不合法");
|
||||
}
|
||||
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
// 先查缓存
|
||||
Cache cache = paramsCache();
|
||||
if (cache != null) {
|
||||
String cached = cache.get(key, String.class);
|
||||
if (StrUtil.isNotBlank(cached)) {
|
||||
return Convert.convert(clazz, cached);
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存未命中,走 Feign
|
||||
RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
|
||||
String result = remoteParamService.getByKey(key).getData();
|
||||
|
||||
// 非空结果写入缓存
|
||||
if (cache != null && StrUtil.isNotBlank(result)) {
|
||||
cache.put(key, result);
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(result)) {
|
||||
return Convert.convert(clazz, result);
|
||||
}
|
||||
|
||||
if (defaultVal.length == 1) {
|
||||
return Convert.convert(clazz, defaultVal.clone()[0]);
|
||||
|
||||
return Convert.convert(clazz, defaultVal[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Cache paramsCache() {
|
||||
return SpringContextHolder.getBean(CacheManager.class).getCache(CacheConstants.PARAMS_DETAILS);
|
||||
}
|
||||
|
||||
}
|
||||
+13
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -47,7 +47,6 @@ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver
|
||||
|
||||
/**
|
||||
* 判断Controller是否包含page 参数
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 是否过滤
|
||||
*/
|
||||
@@ -57,9 +56,9 @@ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parameter 入参集合
|
||||
* @param mavContainer model 和 view
|
||||
* @param webRequest web相关
|
||||
* @param parameter 入参集合
|
||||
* @param mavContainer model 和 view
|
||||
* @param webRequest web相关
|
||||
* @param binderFactory 入参解析
|
||||
* @return 检查后新的page对象
|
||||
* <p>
|
||||
@@ -67,7 +66,7 @@ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
|
||||
|
||||
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
|
||||
@@ -92,15 +91,15 @@ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver
|
||||
|
||||
List<OrderItem> orderItemList = new ArrayList<>();
|
||||
Optional.ofNullable(ascs)
|
||||
.ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(asc -> !SqlInjectionUtils.check(asc))
|
||||
.map(OrderItem::asc)
|
||||
.toList()));
|
||||
.ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(asc -> !SqlInjectionUtils.check(asc))
|
||||
.map(OrderItem::asc)
|
||||
.toList()));
|
||||
Optional.ofNullable(descs)
|
||||
.ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(desc -> !SqlInjectionUtils.check(desc))
|
||||
.map(OrderItem::desc)
|
||||
.toList()));
|
||||
.ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
|
||||
.filter(desc -> !SqlInjectionUtils.check(desc))
|
||||
.map(OrderItem::desc)
|
||||
.toList()));
|
||||
page.addOrder(orderItemList);
|
||||
|
||||
return page;
|
||||
|
||||
+50
-53
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -40,63 +40,60 @@ import java.util.Objects;
|
||||
@RequiredArgsConstructor
|
||||
public class PigxTenantHandler implements TenantLineHandler {
|
||||
|
||||
private final PigxTenantConfigProperties properties;
|
||||
private final PigxTenantConfigProperties properties;
|
||||
|
||||
/**
|
||||
* 获取租户 ID 值表达式,只支持单个 ID 值
|
||||
* <p>
|
||||
*
|
||||
* @return 租户 ID 值表达式
|
||||
*/
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
log.debug("当前租户为 >> {}", tenantId);
|
||||
/**
|
||||
* 获取租户 ID 值表达式,只支持单个 ID 值
|
||||
* <p>
|
||||
* @return 租户 ID 值表达式
|
||||
*/
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
log.debug("当前租户为 >> {}", tenantId);
|
||||
|
||||
if (tenantId == null) {
|
||||
return new NullValue();
|
||||
}
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
if (tenantId == null) {
|
||||
return new NullValue();
|
||||
}
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户字段名
|
||||
*
|
||||
* @return 租户字段名
|
||||
*/
|
||||
@Override
|
||||
public String getTenantIdColumn() {
|
||||
return properties.getColumn();
|
||||
}
|
||||
/**
|
||||
* 获取租户字段名
|
||||
* @return 租户字段名
|
||||
*/
|
||||
@Override
|
||||
public String getTenantIdColumn() {
|
||||
return properties.getColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名判断是否忽略拼接多租户条件
|
||||
* <p>
|
||||
* 默认都要进行解析并拼接多租户条件
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
|
||||
*/
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 判断是否跳过当前查询的租户过滤
|
||||
if (TenantContextHolder.getTenantSkip()) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
/**
|
||||
* 根据表名判断是否忽略拼接多租户条件
|
||||
* <p>
|
||||
* 默认都要进行解析并拼接多租户条件
|
||||
* @param tableName 表名
|
||||
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
|
||||
*/
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 判断是否跳过当前查询的租户过滤
|
||||
if (TenantContextHolder.getTenantSkip()) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
// 租户中ID 为空,查询全部,不进行过滤
|
||||
if (tenantId == null) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
// 租户中ID 为空,查询全部,不进行过滤
|
||||
if (tenantId == null) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
// 判断实体类是否标记为多租户隔离
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||
TenantTable annotation = null;
|
||||
if (Objects.nonNull(tableInfo)) {
|
||||
annotation = AnnotationUtils.findAnnotation(tableInfo.getEntityType(), TenantTable.class);
|
||||
}
|
||||
return !properties.getTables().contains(tableName) && annotation == null;
|
||||
}
|
||||
// 判断实体类是否标记为多租户隔离
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||
TenantTable annotation = null;
|
||||
if (Objects.nonNull(tableInfo)) {
|
||||
annotation = AnnotationUtils.findAnnotation(tableInfo.getEntityType(), TenantTable.class);
|
||||
}
|
||||
return !properties.getTables().contains(tableName) && annotation == null;
|
||||
}
|
||||
|
||||
}
|
||||
+61
-35
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2026, lengleng All rights reserved.
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
@@ -37,7 +37,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -71,25 +70,23 @@ public class TenantContextHolderFilter extends GenericFilterBean {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
String headerTenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String paramTenantId = request.getParameter(CommonConstants.TENANT_ID);
|
||||
|
||||
log.debug("获取header中的租户ID为:{}", headerTenantId);
|
||||
|
||||
if (StrUtil.isNotBlank(headerTenantId) && !StrUtil.equals(UNDEFINED_STR, headerTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(headerTenantId));
|
||||
} else if (StrUtil.isNotBlank(paramTenantId) && !StrUtil.equals(UNDEFINED_STR, paramTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(paramTenantId));
|
||||
} else {
|
||||
TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
|
||||
}
|
||||
|
||||
if (!checkTenantStatus(request, response)) {
|
||||
TenantContextHolder.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String headerTenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String paramTenantId = request.getParameter(CommonConstants.TENANT_ID);
|
||||
|
||||
log.debug("获取header中的租户ID为:{}", headerTenantId);
|
||||
|
||||
if (StrUtil.isNotBlank(headerTenantId) && !StrUtil.equals(UNDEFINED_STR, headerTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(headerTenantId));
|
||||
} else if (StrUtil.isNotBlank(paramTenantId) && !StrUtil.equals(UNDEFINED_STR, paramTenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(paramTenantId));
|
||||
} else {
|
||||
TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
|
||||
}
|
||||
|
||||
if (!checkTenantStatus(request, response)) {
|
||||
return;
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
TenantContextHolder.clear();
|
||||
@@ -110,27 +107,24 @@ public class TenantContextHolderFilter extends GenericFilterBean {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<SysTenant> tenantList = null;
|
||||
|
||||
// 判断租户状态
|
||||
Cache cache = cacheManager.getCache(CacheConstants.TENANT_DETAILS);
|
||||
|
||||
if (Objects.nonNull(cache) && Objects.nonNull(cache.get(SimpleKey.EMPTY, List.class))) {
|
||||
tenantList = cache.get(SimpleKey.EMPTY, List.class);
|
||||
} else {
|
||||
R<List<SysTenant>> tenantListR = remoteTenantService.list();
|
||||
tenantList = tenantListR.getData();
|
||||
List<SysTenant> tenantList;
|
||||
try {
|
||||
tenantList = getTenantList();
|
||||
} catch (Exception ex) {
|
||||
log.error("获取租户列表失败", ex);
|
||||
writeTenantExceptionResponse(response, "租户列表获取失败,请检查租户服务状态");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CollUtil.isEmpty(tenantList)) {
|
||||
writeTenantExceptionResponse(response, "租户列表获取失败,请检查租户服务状态");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前请求的租户ID
|
||||
Long currentTenantId = TenantContextHolder.getTenantId();
|
||||
// 检查租户是否存在且在有效期内
|
||||
boolean exist = tenantList.stream()
|
||||
.filter(tenant -> NumberUtil.equals(tenant.getId(), currentTenantId)) // 匹配租户ID
|
||||
boolean exist = tenantList.stream().filter(tenant -> NumberUtil.equals(tenant.getId(), currentTenantId)) // 匹配租户ID
|
||||
.filter(tenant -> tenant.getStartTime().isBefore(LocalDateTime.now())) // 检查开始时间:租户已生效
|
||||
.anyMatch(tenant -> tenant.getEndTime().isAfter(LocalDateTime.now()));
|
||||
|
||||
@@ -139,10 +133,42 @@ public class TenantContextHolderFilter extends GenericFilterBean {
|
||||
}
|
||||
|
||||
// 如果租户不存在或已失效,返回426状态码和错误信息
|
||||
response.setStatus(HttpStatus.UPGRADE_REQUIRED.value());
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().print(JSONUtil.toJsonStr(R.failed(StrUtil.format("租户ID为{}的租户不存在或已过期失效", currentTenantId))));
|
||||
writeTenantExceptionResponse(response, StrUtil.format("租户ID为{}的租户不存在或已过期失效", currentTenantId));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户列表,缓存不存在时从远程服务读取。
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
private List<SysTenant> getTenantList() {
|
||||
Cache cache = cacheManager.getCache(CacheConstants.TENANT_DETAILS);
|
||||
if (Objects.nonNull(cache)) {
|
||||
List<SysTenant> tenantList = cache.get(SimpleKey.EMPTY, List.class);
|
||||
if (Objects.nonNull(tenantList)) {
|
||||
return tenantList;
|
||||
}
|
||||
}
|
||||
|
||||
R<List<SysTenant>> tenantListR = remoteTenantService.list();
|
||||
return Objects.nonNull(tenantListR) ? tenantListR.getData() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入租户异常响应。
|
||||
*
|
||||
* @param response 响应
|
||||
* @param message 错误信息
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void writeTenantExceptionResponse(HttpServletResponse response, String message) {
|
||||
if (response.isCommitted()) {
|
||||
return;
|
||||
}
|
||||
response.setStatus(HttpStatus.UPGRADE_REQUIRED.value());
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().print(JSONUtil.toJsonStr(R.failed(message)));
|
||||
}
|
||||
|
||||
}
|
||||
+9
-9
@@ -16,15 +16,15 @@ import java.io.IOException;
|
||||
*/
|
||||
public class TenantRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
/**
|
||||
* 拦截HTTP请求,添加租户ID到请求头
|
||||
*
|
||||
* @param request HTTP请求对象
|
||||
* @param body 请求体字节数组
|
||||
* @param execution 请求执行器
|
||||
* @return 客户端HTTP响应
|
||||
* @throws IOException 当I/O操作失败时抛出
|
||||
*/
|
||||
/**
|
||||
* 拦截HTTP请求,添加租户ID到请求头
|
||||
*
|
||||
* @param request HTTP请求对象
|
||||
* @param body 请求体字节数组
|
||||
* @param execution 请求执行器
|
||||
* @return 客户端HTTP响应
|
||||
* @throws IOException 当I/O操作失败时抛出
|
||||
*/
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
|
||||
Reference in new issue
Block a user