完善ruoyi-common-tenant模块

This commit is contained in:
dataprince 2023-12-27 10:47:35 +08:00
parent a03742f481
commit c14f8d098f
8 changed files with 173 additions and 97 deletions

View File

@ -0,0 +1,84 @@
package com.ruoyi.common.tenant.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.tenant.TenantFactory;
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
import com.ruoyi.common.orm.config.MyBatisFlexConfig;
import com.ruoyi.common.redis.config.RedisConfig;
import com.ruoyi.common.redis.config.properties.RedissonProperties;
import com.ruoyi.common.tenant.core.TenantSaTokenDao;
import com.ruoyi.common.tenant.handle.MyTenantFactory;
import com.ruoyi.common.tenant.handle.TenantKeyPrefixHandler;
import com.ruoyi.common.tenant.manager.TenantSpringCacheManager;
import com.ruoyi.common.tenant.properties.TenantProperties;
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.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;
/**
* 租户配置类
*
* @author Lion Li
* @author 数据小王子
*/
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class, MyBatisFlexConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
/**
* 多租户工厂用于获取当前租户ID
*/
@Bean
public TenantFactory tenantFactory() {
return new MyTenantFactory();
}
@Bean
public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
return config -> {
TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
// 设置多租户 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);
}
};
}
/**
* 多租户缓存管理器
*/
@Primary
@Bean
public CacheManager tenantCacheManager() {
return new TenantSpringCacheManager();
}
/**
* 多租户鉴权dao实现
*/
@Primary
@Bean
public SaTokenDao tenantSaTokenDao() {
return new TenantSaTokenDao();
}
}

View File

@ -1,23 +0,0 @@
package com.ruoyi.common.tenant.core;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.orm.core.domain.BaseEntity;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 租户基类
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TenantEntity extends BaseEntity {
/**
* 租户编号
*/
private Long tenantId;
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.common.tenant.handle;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.tenant.TenantFactory;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.tenant.helper.TenantHelper;
import lombok.AllArgsConstructor;
/**
* 自定义租户处理器
*
* @author 数据小王子
*/
@AllArgsConstructor
public class MyTenantFactory implements TenantFactory {
@Override
public Object[] getTenantIds() {
Long tenantId = LoginHelper.getTenantId();
if (ObjectUtil.isNull(tenantId)) {
return null;
}
Long dynamicTenantId = TenantHelper.getDynamic();
if (ObjectUtil.isNotNull(dynamicTenantId)) {
// 返回动态租户
return new Object[]{dynamicTenantId};
}
// 返回固定租户
return new Object[]{tenantId};
}
}

View File

@ -1,57 +0,0 @@
package com.ruoyi.common.tenant.handle;
import cn.hutool.core.collection.ListUtil;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.tenant.properties.TenantProperties;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import java.util.List;
/**
* 自定义租户处理器
*
* @author Lion Li
*/
@AllArgsConstructor
//public class PlusTenantLineHandler implements TenantLineHandler {
public class PlusTenantLineHandler {
private final TenantProperties tenantProperties;
public Expression getTenantId() {
String tenantId = LoginHelper.getTenantId().toString();
if (StringUtils.isBlank(tenantId)) {
return new NullValue();
}
String dynamicTenantId = TenantHelper.getDynamic();
if (StringUtils.isNotBlank(dynamicTenantId)) {
// 返回动态租户
return new StringValue(dynamicTenantId);
}
// 返回固定租户
return new StringValue(tenantId);
}
public boolean ignoreTable(String tableName) {
String tenantId = LoginHelper.getTenantId().toString();
// 判断是否有租户
if (StringUtils.isNotBlank(tenantId)) {
// 不需要过滤租户的表
List<String> excludes = tenantProperties.getExcludes();
// 非业务表
List<String> tables = ListUtil.toList(
"gen_table",
"gen_table_column"
);
tables.addAll(excludes);
return tables.contains(tableName);
}
return true;
}
}

View File

@ -1,15 +1,19 @@
package com.ruoyi.common.tenant.handle; package com.ruoyi.common.tenant.handle;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.GlobalConstants; import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.handler.KeyPrefixHandler; import com.ruoyi.common.redis.handler.KeyPrefixHandler;
import com.ruoyi.common.tenant.helper.TenantHelper; import com.ruoyi.common.tenant.helper.TenantHelper;
import lombok.extern.slf4j.Slf4j;
/** /**
* 多租户redis缓存key前缀处理 * 多租户redis缓存key前缀处理
* *
* @author Lion Li * @author Lion Li
* @author 数据小王子
*/ */
@Slf4j
public class TenantKeyPrefixHandler extends KeyPrefixHandler { public class TenantKeyPrefixHandler extends KeyPrefixHandler {
public TenantKeyPrefixHandler(String keyPrefix) { public TenantKeyPrefixHandler(String keyPrefix) {
@ -27,8 +31,11 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.map(name); return super.map(name);
} }
String tenantId = TenantHelper.getTenantId(); Long tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(name, tenantId)) { if (ObjectUtil.isNull(tenantId)) {
log.error("无法获取有效的租户id -> Null");
}
if (StringUtils.startsWith(name, tenantId + "")) {
// 如果存在则直接返回 // 如果存在则直接返回
return super.map(name); return super.map(name);
} }
@ -47,8 +54,8 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.unmap(name); return super.unmap(name);
} }
String tenantId = TenantHelper.getTenantId(); Long tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(unmap, tenantId)) { if (StringUtils.startsWith(unmap, tenantId + "")) {
// 如果存在则删除 // 如果存在则删除
return unmap.substring((tenantId + ":").length()); return unmap.substring((tenantId + ":").length());
} }

View File

@ -3,7 +3,9 @@ package com.ruoyi.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil; import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
import com.mybatisflex.core.tenant.TenantManager;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -19,6 +21,7 @@ import java.util.function.Supplier;
* 租户助手 * 租户助手
* *
* @author Lion Li * @author Lion Li
* @author 数据小王子
*/ */
@Slf4j @Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
@ -26,7 +29,7 @@ public class TenantHelper {
private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"; 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<Long> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
/** /**
* 租户功能是否启用 * 租户功能是否启用
@ -39,14 +42,14 @@ public class TenantHelper {
* 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭) * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/ */
public static void enableIgnore() { public static void enableIgnore() {
//InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build()); TenantManager.ignoreTenantCondition();
} }
/** /**
* 关闭忽略租户 * 关闭忽略租户
*/ */
public static void disableIgnore() { public static void disableIgnore() {
//InterceptorIgnoreHelper.clearIgnoreStrategy(); TenantManager.restoreTenantCondition();
} }
/** /**
@ -82,7 +85,7 @@ public class TenantHelper {
* <p> * <p>
* 如果为非web环境 那么只在当前线程内生效 * 如果为非web环境 那么只在当前线程内生效
*/ */
public static void setDynamic(String tenantId) { public static void setDynamic(Long tenantId) {
if (!SpringMVCUtil.isWeb()) { if (!SpringMVCUtil.isWeb()) {
TEMP_DYNAMIC_TENANT.set(tenantId); TEMP_DYNAMIC_TENANT.set(tenantId);
return; return;
@ -97,13 +100,13 @@ public class TenantHelper {
* <p> * <p>
* 如果为非web环境 那么只在当前线程内生效 * 如果为非web环境 那么只在当前线程内生效
*/ */
public static String getDynamic() { public static Long getDynamic() {
if (!SpringMVCUtil.isWeb()) { if (!SpringMVCUtil.isWeb()) {
return TEMP_DYNAMIC_TENANT.get(); return TEMP_DYNAMIC_TENANT.get();
} }
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
String tenantId = (String) SaHolder.getStorage().get(cacheKey); Long tenantId = (Long) SaHolder.getStorage().get(cacheKey);
if (StringUtils.isNotBlank(tenantId)) { if (ObjectUtil.isNotNull(tenantId)) {
return tenantId; return tenantId;
} }
tenantId = RedisUtils.getCacheObject(cacheKey); tenantId = RedisUtils.getCacheObject(cacheKey);
@ -124,13 +127,44 @@ public class TenantHelper {
SaHolder.getStorage().delete(cacheKey); SaHolder.getStorage().delete(cacheKey);
} }
/**
* 在动态租户中执行
*
* @param handle 处理执行方法
*/
public static void dynamic(Long tenantId, Runnable handle) {
setDynamic(tenantId);
try {
handle.run();
} finally {
clearDynamic();
}
}
/**
* 在动态租户中执行
*
* @param handle 处理执行方法
*/
public static <T> T dynamic(Long tenantId, Supplier<T> handle) {
setDynamic(tenantId);
try {
return handle.get();
} finally {
clearDynamic();
}
}
/** /**
* 获取当前租户id(动态租户优先) * 获取当前租户id(动态租户优先)
*/ */
public static String getTenantId() { public static Long getTenantId() {
String tenantId = TenantHelper.getDynamic(); if (!isEnable()) {
if (StringUtils.isBlank(tenantId)) { return null;
tenantId = LoginHelper.getTenantId().toString(); }
Long tenantId = TenantHelper.getDynamic();
if (ObjectUtil.isNull(tenantId)) {
tenantId = LoginHelper.getTenantId();
} }
return tenantId; return tenantId;
} }

View File

@ -1,7 +1,5 @@
package com.ruoyi.system.mapper; package com.ruoyi.system.mapper;
import java.util.List;
import com.mybatisflex.core.BaseMapper; import com.mybatisflex.core.BaseMapper;
import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.domain.SysPost;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;