Prechádzať zdrojové kódy

feat(platform): 添加平台数据隔离拦截器和平台助手类

- 在 PlatformDataScopeInterceptor 中新增对 PlatformHelper.ignore() 的支持
- 添加 isIgnorePlatform() 方法通过 MyBatis-Plus 的 IgnoreStrategy 机制判断是否忽略平台隔离
- 新增 PlatformHelper 类提供平台相关辅助功能
- 实现 enableIgnore() 和 disableIgnore() 方法控制平台隔离忽略状态
- 添加 ignore() 方法支持在忽略平台上下文中执行代码块
- 实现动态平台设置功能支持临时切换平台编码
- 提供 getPlatform() 方法获取当前生效的平台编码
hurx 3 mesiacov pred
rodič
commit
0b9162fd63

+ 50 - 6
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlatformDataScopeInterceptor.java

@@ -1,5 +1,6 @@
 package org.dromara.common.mybatis.interceptor;
 
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import lombok.extern.slf4j.Slf4j;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
@@ -25,6 +26,7 @@ import java.util.Set;
 /**
  * 平台数据隔离拦截器
  * 自动在 SELECT 语句中注入 platform_code = '当前平台' 条件
+ * 支持通过 PlatformHelper.ignore() 临时忽略平台隔离
  *
  * @author YourName
  */
@@ -71,12 +73,8 @@ public class PlatformDataScopeInterceptor implements Interceptor {
         "customer_sales_info",
         "com_",
         "product_"
-
-
-        // 注意:前缀匹配需特殊处理(如 qrtz_),见 isIgnoreTable 方法
     ));
 
-
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
         StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
@@ -88,11 +86,16 @@ public class PlatformDataScopeInterceptor implements Interceptor {
             return invocation.proceed();
         }
 
+        // 新增:检查是否应忽略平台数据隔离(由 PlatformHelper.ignore() 触发)
+        if (isIgnorePlatform()) {
+            return invocation.proceed();
+        }
+
         String platform = PlatformContext.getPlatform();
 
         // 总控平台:直接放行,不注入任何条件
         if (SysPlatformCode.MAIN.getCode().equals(platform)) {
-            return invocation.proceed(); // ← 核心:提前返回
+            return invocation.proceed();
         }
 
         // 原有逻辑:仅对非 main 平台生效
@@ -180,10 +183,41 @@ public class PlatformDataScopeInterceptor implements Interceptor {
             return true;
         }
 
-        // 前缀匹配(例如以 "qrtz_" 开头的表)
+        // 前缀匹配
         return tableName.startsWith("qrtz_") || tableName.startsWith("product_") || tableName.startsWith("com_");
     }
 
+    /**
+     * 判断当前是否应忽略平台数据隔离
+     * 复用 MyBatis-Plus 的 IgnoreStrategy.tenantLine 作为开关
+     */
+    private boolean isIgnorePlatform() {
+        try {
+            Object ignoreStrategyLocal = ReflectUtil.getStaticFieldValue(
+                InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"
+            );
+            if (ignoreStrategyLocal instanceof ThreadLocal) {
+                Object strategy = ((ThreadLocal<?>) ignoreStrategyLocal).get();
+                if (strategy != null) {
+                    try {
+                        Field tenantLineField = strategy.getClass().getDeclaredField("tenantLine");
+                        tenantLineField.setAccessible(true);
+                        Boolean tenantLine = (Boolean) tenantLineField.get(strategy);
+                        if (Boolean.TRUE.equals(tenantLine)) {
+                            log.debug("PlatformDataScopeInterceptor: ignored due to tenantLine=true");
+                            return true;
+                        }
+                    } catch (NoSuchFieldException | IllegalAccessException ignored) {
+                        // 字段不存在或无法访问,视为未忽略
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("Failed to check ignore platform status", e);
+        }
+        return false;
+    }
+
     @Override
     public Object plugin(Object target) {
         return Plugin.wrap(target, this);
@@ -216,6 +250,16 @@ public class PlatformDataScopeInterceptor implements Interceptor {
             }
         }
 
+        public static Object getStaticFieldValue(Class<?> clazz, String fieldName) {
+            try {
+                Field field = getField(clazz, fieldName);
+                field.setAccessible(true);
+                return field.get(null);
+            } catch (Exception e) {
+                throw new RuntimeException("Get static field value error: " + fieldName, e);
+            }
+        }
+
         private static Field getField(Class<?> clazz, String fieldName) {
             while (clazz != null) {
                 try {

+ 213 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/PlatformHelper.java

@@ -0,0 +1,213 @@
+package org.dromara.common.tenant.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+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.context.PlatformContext;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.Stack;
+import java.util.function.Supplier;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PlatformHelper {
+
+    private static final String DYNAMIC_PLATFORM_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicPlatform";
+
+    private static final ThreadLocal<String> TEMP_DYNAMIC_PLATFORM = new ThreadLocal<>();
+
+    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
+
+    /**
+     * 平台功能是否启用
+     */
+    public static boolean isEnable() {
+        return Convert.toBool(SpringUtils.getProperty("platform.enable"), false);
+    }
+
+    /**
+     * 开启忽略平台(需手动调用 disableIgnore 关闭)
+     * 复用 tenantLine 作为平台忽略标志(与 PlatformDataScopeInterceptor 配合)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+        Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+        reentrantStack.push(reentrantStack.size() + 1);
+    }
+
+    /**
+     * 关闭忽略平台
+     */
+    public static void disableIgnore() {
+        IgnoreStrategy current = InterceptorIgnoreHelper.getIgnoreStrategy("tenant");
+        if (current == null) {
+            return;
+        }
+
+        Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+        boolean isOutermost = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
+
+        if (!isOutermost) {
+            // 嵌套调用,仅关闭 tenantLine
+            current.setTenantLine(false);
+            return;
+        }
+
+        // 判断是否还有其他忽略项
+        boolean hasOtherIgnore = Boolean.TRUE.equals(current.getDynamicTableName())
+            || Boolean.TRUE.equals(current.getBlockAttack())
+            || Boolean.TRUE.equals(current.getIllegalSql())
+            || Boolean.TRUE.equals(current.getDataPermission())
+            || CollectionUtil.isNotEmpty(current.getOthers());
+
+        if (hasOtherIgnore) {
+            // 保留其他忽略项,仅关闭 tenantLine
+            current.setTenantLine(false);
+        } else {
+            // 完全无忽略项,清除整个策略
+            InterceptorIgnoreHelper.clearIgnoreStrategy();
+        }
+    }
+
+    /**
+     * 在忽略平台上下文中执行(无返回值)
+     */
+    public static void ignore(Runnable handle) {
+        enableIgnore();
+        try {
+            handle.run();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 在忽略平台上下文中执行(有返回值)
+     */
+    public static <T> T ignore(Supplier<T> handle) {
+        enableIgnore();
+        try {
+            return handle.get();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 设置动态平台(仅当前线程或全局)
+     *
+     * @param platformCode 平台编码,如 "web", "app"
+     * @param global       是否全局生效(需用户已登录)
+     */
+    public static void setDynamic(String platformCode, boolean global) {
+        if (!isEnable()) {
+            return;
+        }
+        if (!global || !LoginHelper.isLogin()) {
+            TEMP_DYNAMIC_PLATFORM.set(platformCode);
+            return;
+        }
+
+        String cacheKey = DYNAMIC_PLATFORM_KEY + ":" + LoginHelper.getUserId();
+        RedisUtils.setCacheObject(cacheKey, platformCode);
+        SaHolder.getStorage().set(cacheKey, platformCode);
+    }
+
+    public static void setDynamic(String platformCode) {
+        setDynamic(platformCode, false);
+    }
+
+    /**
+     * 获取动态平台
+     */
+    public static String getDynamic() {
+        if (!isEnable()) {
+            return null;
+        }
+        String platform = TEMP_DYNAMIC_PLATFORM.get();
+        if (StringUtils.isNotBlank(platform)) {
+            return platform;
+        }
+
+        SaStorage storage = SaHolder.getStorage();
+        if (storage == null || LoginHelper.getUserId() == null) {
+            return null;
+        }
+
+        String cacheKey = DYNAMIC_PLATFORM_KEY + ":" + LoginHelper.getUserId();
+        platform = storage.getString(cacheKey);
+        if (StringUtils.isNotBlank(platform)) {
+            return "-1".equals(platform) ? null : platform;
+        }
+
+        platform = RedisUtils.getCacheObject(cacheKey);
+        storage.set(cacheKey, StringUtils.isBlank(platform) ? "-1" : platform);
+        return platform;
+    }
+
+    /**
+     * 清除动态平台
+     */
+    public static void clearDynamic() {
+        if (!isEnable()) {
+            return;
+        }
+        TEMP_DYNAMIC_PLATFORM.remove();
+
+        SaStorage storage = SaHolder.getStorage();
+        if (storage != null && LoginHelper.getUserId() != null) {
+            String cacheKey = DYNAMIC_PLATFORM_KEY + ":" + LoginHelper.getUserId();
+            RedisUtils.deleteObject(cacheKey);
+            storage.delete(cacheKey);
+        }
+    }
+
+    /**
+     * 在指定动态平台下执行(自动清理)
+     */
+    public static void dynamic(String platformCode, Runnable handle) {
+        setDynamic(platformCode);
+        try {
+            handle.run();
+        } finally {
+            clearDynamic();
+        }
+    }
+
+    public static <T> T dynamic(String platformCode, Supplier<T> handle) {
+        setDynamic(platformCode);
+        try {
+            return handle.get();
+        } finally {
+            clearDynamic();
+        }
+    }
+
+    /**
+     * 获取当前平台(动态平台优先,否则从 PlatformContext 获取)
+     *
+     * @return 当前生效的 platformCode
+     */
+    public static String getPlatform() {
+        if (!isEnable()) {
+            return null;
+        }
+        String platform = getDynamic();
+        if (StringUtils.isBlank(platform)) {
+            platform = PlatformContext.getPlatform();
+        }
+        return platform;
+    }
+}