package org.dromara.common.tenant.helper; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.constant.GlobalConstants; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.satoken.utils.LoginHelper; import java.util.Stack; import java.util.function.Supplier; /** * 租户助手 * * @author Lion Li */ @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class TenantHelper { private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"; private static final ThreadLocal TEMP_DYNAMIC_TENANT = new ThreadLocal<>(); private static final ThreadLocal> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new); /** * 租户功能是否启用 */ public static boolean isEnable() { 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() { IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); if (ObjectUtil.isNull(ignoreStrategy)) { InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build()); } else { ignoreStrategy.setTenantLine(true); } Stack reentrantStack = REENTRANT_IGNORE.get(); reentrantStack.push(reentrantStack.size() + 1); } /** * 关闭忽略租户 */ public static void disableIgnore() { 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 reentrantStack = REENTRANT_IGNORE.get(); boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1; if (noOtherIgnoreStrategy && empty) { InterceptorIgnoreHelper.clearIgnoreStrategy(); } else if (empty) { ignoreStrategy.setTenantLine(false); } } } /** * 在忽略租户中执行 * * @param handle 处理执行方法 */ public static void ignore(Runnable handle) { enableIgnore(); try { handle.run(); } finally { disableIgnore(); } } /** * 在忽略租户中执行 * * @param handle 处理执行方法 */ public static T ignore(Supplier handle) { enableIgnore(); try { return handle.get(); } finally { disableIgnore(); } } public static void setDynamic(String tenantId) { setDynamic(tenantId, false); } /** * 设置动态租户(一直有效 需要手动清理) *

* 如果为未登录状态下 那么只在当前线程内生效 * * @param tenantId 租户id * @param global 是否全局生效 */ public static void setDynamic(String tenantId, boolean global) { if (!isEnable()) { return; } if (!isLogin() || !global) { TEMP_DYNAMIC_TENANT.set(tenantId); return; } String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); RedisUtils.setCacheObject(cacheKey, tenantId); } /** * 获取动态租户(一直有效 需要手动清理) *

* 如果为未登录状态下 那么只在当前线程内生效 */ public static String getDynamic() { if (!isEnable()) { return null; } if (!isLogin()) { return TEMP_DYNAMIC_TENANT.get(); } // 如果线程内有值 优先返回 String tenantId = TEMP_DYNAMIC_TENANT.get(); if (StringUtils.isNotBlank(tenantId)) { return tenantId; } String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); tenantId = RedisUtils.getCacheObject(cacheKey); return tenantId; } /** * 清除动态租户 */ public static void clearDynamic() { if (!isEnable()) { return; } if (!isLogin()) { TEMP_DYNAMIC_TENANT.remove(); return; } TEMP_DYNAMIC_TENANT.remove(); String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); RedisUtils.deleteObject(cacheKey); } /** * 在动态租户中执行 * * @param handle 处理执行方法 */ public static void dynamic(String tenantId, Runnable handle) { setDynamic(tenantId); try { handle.run(); } finally { clearDynamic(); } } /** * 在动态租户中执行 * * @param handle 处理执行方法 */ public static T dynamic(String tenantId, Supplier 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; } private static boolean isLogin() { try { StpUtil.checkLogin(); return true; } catch (Exception e) { return false; } } }