v3.0.0 init

This commit is contained in:
ageerle
2026-02-06 03:00:23 +08:00
parent eb2e8f3ff8
commit 7b8cfe02a1
1524 changed files with 53132 additions and 58866 deletions

View File

@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
<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>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -20,6 +19,7 @@
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
@@ -27,11 +27,6 @@
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -2,12 +2,7 @@ package org.ruoyi.common.tenant.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.ruoyi.common.core.utils.reflect.ReflectUtils;
import org.ruoyi.common.redis.config.RedisConfig;
import org.ruoyi.common.redis.config.properties.RedissonProperties;
@@ -16,46 +11,39 @@ import org.ruoyi.common.tenant.handle.PlusTenantLineHandler;
import org.ruoyi.common.tenant.handle.TenantKeyPrefixHandler;
import org.ruoyi.common.tenant.manager.TenantSpringCacheManager;
import org.ruoyi.common.tenant.properties.TenantProperties;
import org.ruoyi.config.MybatisPlusConfig;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.ArrayList;
import java.util.List;
/**
* 租户配置类
*
* @author Lion Li
*/
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class})
@AutoConfiguration(after = {RedisConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
/**
* 初始化租户配置
*/
@Bean
public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor,
TenantProperties tenantProperties) {
List<InnerInterceptor> interceptors = new ArrayList<>();
// 多租户插件 必须放到第一位
interceptors.add(tenantLineInnerInterceptor(tenantProperties));
interceptors.addAll(mybatisPlusInterceptor.getInterceptors());
mybatisPlusInterceptor.setInterceptors(interceptors);
return true;
}
@ConditionalOnClass(TenantLineInnerInterceptor.class)
@AutoConfiguration
static class MybatisPlusConfiguration {
/**
* 多租户插件
*/
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
}
/**
* 多租户插件
*/
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
}
@Bean
@@ -67,14 +55,12 @@ public class TenantConfig {
// 使用单机模式
// 设置多租户 redis key前缀
singleServerConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
}
ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
// 集群配置方式 参考下方注释
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 设置多租户 redis key前缀
clusterServersConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
}
};
}

View File

@@ -1,9 +1,8 @@
package org.ruoyi.common.tenant.core;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
/**
* 租户基类

View File

@@ -81,6 +81,17 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@Override
public <T> T getObject(String key, Class<T> classType) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
}
/**
* 写入Object并设定存活时间 (单位: 秒)
*/
@@ -137,7 +148,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
}
/**
* 搜索数据
*/

View File

@@ -3,11 +3,11 @@ package org.ruoyi.common.tenant.handle;
import cn.hutool.core.collection.ListUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.tenant.helper.TenantHelper;
import org.ruoyi.common.tenant.properties.TenantProperties;
@@ -18,6 +18,7 @@ import java.util.List;
*
* @author Lion Li
*/
@Slf4j
@AllArgsConstructor
public class PlusTenantLineHandler implements TenantLineHandler {
@@ -25,33 +26,29 @@ public class PlusTenantLineHandler implements TenantLineHandler {
@Override
public Expression getTenantId() {
String tenantId = LoginHelper.getTenantId();
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.error("无法获取有效的租户id -> Null");
return new NullValue();
}
String dynamicTenantId = TenantHelper.getDynamic();
if (StringUtils.isNotBlank(dynamicTenantId)) {
// 返回动态租户
return new StringValue(dynamicTenantId);
}
// 返回固定租户
return new StringValue(tenantId);
}
@Override
public boolean ignoreTable(String tableName) {
String tenantId = LoginHelper.getTenantId();
String tenantId = TenantHelper.getTenantId();
// 判断是否有租户
if (StringUtils.isNotBlank(tenantId)) {
// 不需要过滤租户的表
List<String> excludes = tenantProperties.getExcludes();
// 非业务表
List<String> tables = ListUtil.toList(
"gen_table",
"gen_table_column"
"gen_table",
"gen_table_column"
);
tables.addAll(excludes);
return tables.contains(tableName);
return StringUtils.equalsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
}
return true;
}

View File

@@ -1,5 +1,7 @@
package org.ruoyi.common.tenant.handle;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.redis.handler.KeyPrefixHandler;
@@ -10,6 +12,7 @@ import org.ruoyi.common.tenant.helper.TenantHelper;
*
* @author Lion Li
*/
@Slf4j
public class TenantKeyPrefixHandler extends KeyPrefixHandler {
public TenantKeyPrefixHandler(String keyPrefix) {
@@ -24,11 +27,22 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.isBlank(name)) {
return null;
}
try {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.map(name);
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.map(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(name, tenantId)) {
if (StringUtils.isBlank(tenantId)) {
log.debug("无法获取有效的租户id -> Null");
return super.map(name);
}
if (StringUtils.startsWith(name, tenantId + "")) {
// 如果存在则直接返回
return super.map(name);
}
@@ -44,11 +58,22 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.isBlank(unmap)) {
return null;
}
try {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return unmap;
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.unmap(name);
return unmap;
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(unmap, tenantId)) {
if (StringUtils.isBlank(tenantId)) {
log.debug("无法获取有效的租户id -> Null");
return unmap;
}
if (StringUtils.startsWith(unmap, tenantId + "")) {
// 如果存在则删除
return unmap.substring((tenantId + ":").length());
}

View File

@@ -1,9 +1,10 @@
package org.ruoyi.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
@@ -12,9 +13,11 @@ import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.core.utils.reflect.ReflectUtils;
import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import java.util.Stack;
import java.util.function.Supplier;
/**
@@ -28,7 +31,9 @@ public class TenantHelper {
private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new ThreadLocal<>();
private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
/**
* 租户功能是否启用
@@ -37,18 +42,49 @@ public class TenantHelper {
return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
}
private static IgnoreStrategy getIgnoreStrategy() {
Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
return ignoreStrategy;
}
}
return null;
}
/**
* 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
public static void enableIgnore() {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNull(ignoreStrategy)) {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
} else {
ignoreStrategy.setTenantLine(true);
}
Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
reentrantStack.push(reentrantStack.size() + 1);
}
/**
* 关闭忽略租户
*/
public static void disableIgnore() {
InterceptorIgnoreHelper.clearIgnoreStrategy();
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNotNull(ignoreStrategy)) {
boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
&& !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
&& !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
&& !Boolean.TRUE.equals(ignoreStrategy.getDataPermission())
&& CollectionUtil.isEmpty(ignoreStrategy.getOthers());
Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
if (noOtherIgnoreStrategy && empty) {
InterceptorIgnoreHelper.clearIgnoreStrategy();
} else if (empty) {
ignoreStrategy.setTenantLine(false);
}
}
}
/**
@@ -79,6 +115,10 @@ public class TenantHelper {
}
}
public static void setDynamic(String tenantId) {
setDynamic(tenantId, false);
}
/**
* 设置动态租户(一直有效 需要手动清理)
* <p>
@@ -103,61 +143,49 @@ public class TenantHelper {
/**
* 获取动态租户(一直有效 需要手动清理)
* <p>
* 如果为非web环境 那么只在当前线程内生效
* 如果为未登录状态下 那么只在当前线程内生效
*/
public static String getDynamic() {
if (!SpringMVCUtil.isWeb()) {
if (!isEnable()) {
return null;
}
if (!LoginHelper.isLogin()) {
return TEMP_DYNAMIC_TENANT.get();
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
String tenantId = (String) SaHolder.getStorage().get(cacheKey);
// 如果线程内有值 优先返回
String tenantId = TEMP_DYNAMIC_TENANT.get();
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
tenantId = RedisUtils.getCacheObject(cacheKey);
SaHolder.getStorage().set(cacheKey, tenantId);
return tenantId;
}
/**
* 设置动态租户(一直有效 需要手动清理)
* <p>
* 如果为非web环境 那么只在当前线程内生效
*/
public static void setDynamic(String tenantId) {
if (!SpringMVCUtil.isWeb()) {
TEMP_DYNAMIC_TENANT.set(tenantId);
return;
}
SaStorage storage = SaHolder.getStorage();
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.setCacheObject(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
tenantId = storage.getString(cacheKey);
// 如果为 -1 说明已经查过redis并且不存在值 则直接返回null
if (StringUtils.isNotBlank(tenantId)) {
return tenantId.equals("-1") ? null : tenantId;
}
tenantId = RedisUtils.getCacheObject(cacheKey);
storage.set(cacheKey, StringUtils.isBlank(tenantId) ? "-1" : tenantId);
return tenantId;
}
/**
* 清除动态租户
*/
public static void clearDynamic() {
if (!SpringMVCUtil.isWeb()) {
if (!isEnable()) {
return;
}
if (!LoginHelper.isLogin()) {
TEMP_DYNAMIC_TENANT.remove();
return;
}
TEMP_DYNAMIC_TENANT.remove();
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.deleteObject(cacheKey);
SaHolder.getStorage().delete(cacheKey);
}
/**
* 获取当前租户id(动态租户优先)
*/
public static String getTenantId() {
String tenantId = TenantHelper.getDynamic();
if (StringUtils.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
return tenantId;
}
/**
* 在动态租户中执行
*
@@ -172,4 +200,32 @@ public class TenantHelper {
}
}
/**
* 在动态租户中执行
*
* @param handle 处理执行方法
*/
public static <T> T dynamic(String tenantId, Supplier<T> handle) {
setDynamic(tenantId);
try {
return handle.get();
} finally {
clearDynamic();
}
}
/**
* 获取当前租户id(动态租户优先)
*/
public static String getTenantId() {
if (!isEnable()) {
return null;
}
String tenantId = TenantHelper.getDynamic();
if (StringUtils.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
return tenantId;
}
}

View File

@@ -1,5 +1,7 @@
package org.ruoyi.common.tenant.manager;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.redis.manager.PlusSpringCacheManager;
@@ -11,6 +13,7 @@ import org.springframework.cache.Cache;
*
* @author Lion Li
*/
@Slf4j
public class TenantSpringCacheManager extends PlusSpringCacheManager {
public TenantSpringCacheManager() {
@@ -18,10 +21,16 @@ public class TenantSpringCacheManager extends PlusSpringCacheManager {
@Override
public Cache getCache(String name) {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.getCache(name);
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.getCache(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.error("无法获取有效的租户id -> Null");
}
if (StringUtils.startsWith(name, tenantId)) {
// 如果存在则直接返回
return super.getCache(name);