瀏覽代碼

add 简单迁移租户模块,修改相关功能

Yjoioooo 3 年之前
父節點
當前提交
5e4e7ba73a
共有 31 個文件被更改,包括 782 次插入22 次删除
  1. 8 0
      pom.xml
  2. 1 2
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java
  3. 1 1
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java
  4. 5 0
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUser.java
  5. 1 0
      ruoyi-common/pom.xml
  6. 7 0
      ruoyi-common/ruoyi-common-bom/pom.xml
  7. 39 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java
  8. 45 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java
  9. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java
  10. 1 1
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java
  11. 6 0
      ruoyi-common/ruoyi-common-satoken/pom.xml
  12. 44 6
      ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java
  13. 36 0
      ruoyi-common/ruoyi-common-tenant/pom.xml
  14. 100 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java
  15. 21 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java
  16. 149 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java
  17. 18 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java
  18. 58 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java
  19. 59 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java
  20. 110 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java
  21. 33 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java
  22. 27 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java
  23. 1 0
      ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  24. 1 1
      ruoyi-example/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/SensitiveServiceImpl.java
  25. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java
  26. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
  27. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
  28. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPermissionServiceImpl.java
  29. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
  30. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysSensitiveServiceImpl.java
  31. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

+ 8 - 0
pom.xml

@@ -41,6 +41,7 @@
         <elasticsearch.version>7.14.0</elasticsearch.version>
         <skywalking-toolkit.version>8.14.0</skywalking-toolkit.version>
         <bouncycastle.version>1.72</bouncycastle.version>
+        <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
         <!-- 离线IP地址定位库 -->
         <ip2region.version>2.7.0</ip2region.version>
 
@@ -357,6 +358,13 @@
                 <version>${fastjson.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${alibaba-ttl.version}</version>
+            </dependency>
+
+
         </dependencies>
     </dependencyManagement>
 

+ 1 - 2
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java

@@ -17,7 +17,6 @@ import lombok.NoArgsConstructor;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
-import java.util.Set;
 
 /**
  * 角色表 sys_role
@@ -123,7 +122,7 @@ public class SysRole extends BaseEntity {
      * 是否管理员
      */
     public boolean isAdmin() {
-        return UserConstants.ADMIN_ID.equals(this.roleId);
+        return UserConstants.SUPER_ADMIN_ID.equals(this.roleId);
     }
 
 }

+ 1 - 1
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java

@@ -165,7 +165,7 @@ public class SysUser extends BaseEntity {
      * 是否管理员
      */
     public boolean isAdmin() {
-        return UserConstants.ADMIN_ID.equals(this.userId);
+        return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
     }
 
 }

+ 5 - 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUser.java

@@ -19,6 +19,11 @@ import java.util.Set;
 public class LoginUser implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
     /**
      * 用户ID
      */

+ 1 - 0
ruoyi-common/pom.xml

@@ -36,6 +36,7 @@
         <module>ruoyi-common-prometheus</module>
         <module>ruoyi-common-translation</module>
         <module>ruoyi-common-encrypt</module>
+        <module>ruoyi-common-tenant</module>
     </modules>
 
     <artifactId>ruoyi-common</artifactId>

+ 7 - 0
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -174,6 +174,13 @@
                 <version>${project.version}</version>
             </dependency>
 
+            <!-- 租户模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common-tenant</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 </project>

+ 39 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+    /**
+     * 全局 redis key (业务无关的key)
+     */
+    String GLOBAL_REDIS_KEY = "global:";
+
+    /**
+     * 登录用户 redis key
+     */
+    String LOGIN_TOKEN_KEY = GLOBAL_REDIS_KEY + "Authorization:login:token:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+}

+ 45 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java

@@ -0,0 +1,45 @@
+package com.ruoyi.common.core.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+    /**
+     * 租户正常状态
+     */
+    String NORMAL = "0";
+
+    /**
+     * 租户封禁状态
+     */
+    String DISABLE = "1";
+
+    /**
+     * 超级管理员ID
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+    /**
+     * 超级管理员角色 roleKey
+     */
+    String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+    /**
+     * 租户管理员角色 roleKey
+     */
+    String TENANT_ADMIN_ROLE_KEY = "admin";
+
+    /**
+     * 租户管理员角色名称
+     */
+    String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+    /**
+     * 默认租户ID
+     */
+    String DEFAULT_TENANT_ID = "000000";
+
+}

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java

@@ -128,5 +128,5 @@ public interface UserConstants {
     /**
      * 管理员ID
      */
-    Long ADMIN_ID = 1L;
+    Long SUPER_ADMIN_ID = 1L;
 }

+ 1 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java

@@ -79,7 +79,7 @@ public class PlusDataPermissionHandler {
             DataPermissionHelper.setVariable("user", currentUser);
         }
         // 如果是超级管理员,则不过滤数据
-        if (ObjectUtil.isNull(currentUser) || LoginHelper.isAdmin(currentUser.getUserId())) {
+        if (ObjectUtil.isNull(currentUser) || LoginHelper.isSuperAdmin(currentUser.getUserId())) {
             return where;
         }
         String dataFilterSql = buildDataFilter(dataColumns, isSelect);

+ 6 - 0
ruoyi-common/ruoyi-common-satoken/pom.xml

@@ -52,6 +52,12 @@
             <artifactId>ruoyi-common-redis</artifactId>
         </dependency>
 
+        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 44 - 6
ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java

@@ -6,6 +6,7 @@ import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.core.constant.TenantConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.enums.DeviceType;
 import com.ruoyi.common.core.enums.UserType;
@@ -13,6 +14,8 @@ import com.ruoyi.system.api.model.LoginUser;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
+import java.util.Set;
+
 /**
  * 登录鉴权助手
  * <p>
@@ -29,6 +32,7 @@ import lombok.NoArgsConstructor;
 public class LoginHelper {
 
     public static final String LOGIN_USER_KEY = "loginUser";
+    public static final String TENANT_KEY = "tenantId";
     public static final String USER_KEY = "userId";
 
     /**
@@ -49,12 +53,15 @@ public class LoginHelper {
     public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
         SaStorage storage = SaHolder.getStorage();
         storage.set(LOGIN_USER_KEY, loginUser);
+        storage.set(TENANT_KEY, loginUser.getTenantId());
         storage.set(USER_KEY, loginUser.getUserId());
         SaLoginModel model = new SaLoginModel();
         if (ObjectUtil.isNotNull(deviceType)) {
             model.setDevice(deviceType.getDevice());
         }
-        StpUtil.login(loginUser.getLoginId(), model.setExtra(USER_KEY, loginUser.getUserId()));
+        StpUtil.login(loginUser.getLoginId(),
+            model.setExtra(TENANT_KEY, loginUser.getTenantId())
+                .setExtra(USER_KEY, loginUser.getUserId()));
         StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
     }
 
@@ -95,6 +102,23 @@ public class LoginHelper {
         return userId;
     }
 
+    /**
+     * 获取租户ID
+     */
+    public static String getTenantId() {
+        String tenantId;
+        try {
+            tenantId = (String) SaHolder.getStorage().get(TENANT_KEY);
+            if (ObjectUtil.isNull(tenantId)) {
+                tenantId = (String) StpUtil.getExtra(TENANT_KEY);
+                SaHolder.getStorage().set(TENANT_KEY, tenantId);
+            }
+        } catch (Exception e) {
+            return null;
+        }
+        return tenantId;
+    }
+
     /**
      * 获取部门ID
      */
@@ -118,17 +142,31 @@ public class LoginHelper {
     }
 
     /**
-     * 是否为管理员
+     * 是否为超级管理员
      *
      * @param userId 用户ID
      * @return 结果
      */
-    public static boolean isAdmin(Long userId) {
-        return UserConstants.ADMIN_ID.equals(userId);
+    public static boolean isSuperAdmin(Long userId) {
+        return UserConstants.SUPER_ADMIN_ID.equals(userId);
+    }
+
+    public static boolean isSuperAdmin() {
+        return isSuperAdmin(getUserId());
+    }
+
+    /**
+     * 是否为超级管理员
+     *
+     * @param rolePermission 角色权限标识组
+     * @return 结果
+     */
+    public static boolean isTenantAdmin(Set<String> rolePermission) {
+        return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
     }
 
-    public static boolean isAdmin() {
-        return isAdmin(getUserId());
+    public static boolean isTenantAdmin() {
+        return isTenantAdmin(getLoginUser().getRolePermission());
     }
 
 }

+ 36 - 0
ruoyi-common/ruoyi-common-tenant/pom.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>com.ruoyi</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>1.6.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-tenant</artifactId>
+
+    <description>
+        ruoyi-common-tenant 租户模块
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 100 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java

@@ -0,0 +1,100 @@
+package com.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 com.ruoyi.common.core.utils.reflect.ReflectUtils;
+import com.ruoyi.common.mybatis.config.MybatisPlusConfiguration;
+import com.ruoyi.common.redis.config.RedisConfiguration;
+import com.ruoyi.common.redis.config.properties.RedissonProperties;
+import com.ruoyi.common.tenant.core.TenantSaTokenDao;
+import com.ruoyi.common.tenant.handle.PlusTenantLineHandler;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 租户配置类
+ *
+ * @author Lion Li
+ */
+@EnableConfigurationProperties(TenantProperties.class)
+@AutoConfiguration(after = {RedisConfiguration.class, MybatisPlusConfiguration.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;
+    }
+
+    /**
+     * 多租户插件
+     */
+    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
+        return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
+    }
+
+    @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();
+    }
+
+}

+ 21 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.tenant.core;
+
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 租户基类
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantEntity extends BaseEntity {
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+}

+ 149 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java

@@ -0,0 +1,149 @@
+package com.ruoyi.common.tenant.core;
+
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * SaToken 认证数据持久层 适配多租户
+ *
+ * @author Lion Li
+ */
+public class TenantSaTokenDao extends PlusSaTokenDao {
+
+    @Override
+    public String get(String key) {
+        return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    @Override
+    public void set(String key, String value, long timeout) {
+        super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
+    }
+
+    /**
+     * 修修改指定key-value键值对 (过期时间不变)
+     */
+    @Override
+    public void update(String key, String value) {
+        long expire = getTimeout(key);
+        // -2 = 无此键
+        if (expire == NOT_VALUE_EXPIRE) {
+            return;
+        }
+        this.set(key, value, expire);
+    }
+
+    /**
+     * 删除Value
+     */
+    @Override
+    public void delete(String key) {
+        super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 获取Value的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public long getTimeout(String key) {
+        return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 修改Value的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public void updateTimeout(String key, long timeout) {
+        // 判断是否想要设置为永久
+        if (timeout == NEVER_EXPIRE) {
+            long expire = getTimeout(key);
+            if (expire == NEVER_EXPIRE) {
+                // 如果其已经被设置为永久,则不作任何处理
+            } else {
+                // 如果尚未被设置为永久,那么再次set一次
+                this.set(key, this.get(key), timeout);
+            }
+            return;
+        }
+        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 获取Object,如无返空
+     */
+    @Override
+    public Object getObject(String key) {
+        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 写入Object,并设定存活时间 (单位: 秒)
+     */
+    @Override
+    public void setObject(String key, Object object, long timeout) {
+        super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
+    }
+
+    /**
+     * 更新Object (过期时间不变)
+     */
+    @Override
+    public void updateObject(String key, Object object) {
+        long expire = getObjectTimeout(key);
+        // -2 = 无此键
+        if (expire == NOT_VALUE_EXPIRE) {
+            return;
+        }
+        this.setObject(key, object, expire);
+    }
+
+    /**
+     * 删除Object
+     */
+    @Override
+    public void deleteObject(String key) {
+        super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 获取Object的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public long getObjectTimeout(String key) {
+        return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 修改Object的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public void updateObjectTimeout(String key, long timeout) {
+        // 判断是否想要设置为永久
+        if (timeout == NEVER_EXPIRE) {
+            long expire = getObjectTimeout(key);
+            if (expire == NEVER_EXPIRE) {
+                // 如果其已经被设置为永久,则不作任何处理
+            } else {
+                // 如果尚未被设置为永久,那么再次set一次
+                this.setObject(key, this.getObject(key), timeout);
+            }
+            return;
+        }
+        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 搜索数据
+     */
+    @Override
+    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+        return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
+    }
+}

+ 18 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java

@@ -0,0 +1,18 @@
+package com.ruoyi.common.tenant.exception;
+
+
+import com.ruoyi.common.core.exception.base.BaseException;
+
+/**
+ * 租户异常类
+ *
+ * @author Lion Li
+ */
+public class TenantException extends BaseException {
+
+    private static final long serialVersionUID = 1L;
+
+    public TenantException(String code, Object... args) {
+        super("tenant", code, args, null);
+    }
+}

+ 58 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java

@@ -0,0 +1,58 @@
+package com.ruoyi.common.tenant.handle;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.ruoyi.common.core.utils.StringUtils;
+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 com.ruoyi.common.satoken.utils.LoginHelper;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 自定义租户处理器
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+public class PlusTenantLineHandler implements TenantLineHandler {
+
+    private final TenantProperties tenantProperties;
+
+    @Override
+    public Expression getTenantId() {
+        String tenantId = LoginHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            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();
+        // 判断是否有租户
+        if (StringUtils.isNotBlank(tenantId)) {
+            // 不需要过滤租户的表
+            List<String> excludes = tenantProperties.getExcludes();
+            // 非业务表
+            excludes.addAll(Arrays.asList(
+                    "gen_table",
+                    "gen_table_column"
+            ));
+            return excludes.contains(tableName);
+        }
+        return true;
+    }
+
+}

+ 59 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java

@@ -0,0 +1,59 @@
+package com.ruoyi.common.tenant.handle;
+
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.handler.KeyPrefixHandler;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+
+/**
+ * 多租户redis缓存key前缀处理
+ *
+ * @author Lion Li
+ */
+public class TenantKeyPrefixHandler extends KeyPrefixHandler {
+
+    public TenantKeyPrefixHandler(String keyPrefix) {
+        super(keyPrefix);
+    }
+
+    /**
+     * 增加前缀
+     */
+    @Override
+    public String map(String name) {
+        if (StringUtils.isBlank(name)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.map(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(name, tenantId)) {
+            // 如果存在则直接返回
+            return super.map(name);
+        }
+        return super.map(tenantId + ":" + name);
+    }
+
+    /**
+     * 去除前缀
+     */
+    @Override
+    public String unmap(String name) {
+        String unmap = super.unmap(name);
+        if (StringUtils.isBlank(unmap)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.unmap(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(unmap, tenantId)) {
+            // 如果存在则删除
+            return unmap.substring((tenantId + ":").length());
+        }
+        return unmap;
+    }
+
+}

+ 110 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java

@@ -0,0 +1,110 @@
+package com.ruoyi.common.tenant.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.spring.SpringMVCUtil;
+import cn.hutool.core.convert.Convert;
+import com.alibaba.ttl.TransmittableThreadLocal;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.SpringUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.common.satoken.utils.LoginHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 租户助手
+ *
+ * @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<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
+
+    /**
+     * 租户功能是否启用
+     */
+    public static boolean isEnable() {
+        return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
+    }
+
+    /**
+     * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+    }
+
+    /**
+     * 关闭忽略租户
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
+    /**
+     * 设置动态租户(一直有效 需要手动清理)
+     * <p>
+     * 如果为非web环境 那么只在当前线程内生效
+     */
+    public static void setDynamic(String tenantId) {
+        if (!SpringMVCUtil.isWeb()) {
+            TEMP_DYNAMIC_TENANT.set(tenantId);
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        RedisUtils.setCacheObject(cacheKey, tenantId);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+    }
+
+    /**
+     * 获取动态租户(一直有效 需要手动清理)
+     * <p>
+     * 如果为非web环境 那么只在当前线程内生效
+     */
+    public static String getDynamic() {
+        if (!SpringMVCUtil.isWeb()) {
+            return TEMP_DYNAMIC_TENANT.get();
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        String tenantId = (String) SaHolder.getStorage().get(cacheKey);
+        if (StringUtils.isNotBlank(tenantId)) {
+            return tenantId;
+        }
+        tenantId = RedisUtils.getCacheObject(cacheKey);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+        return tenantId;
+    }
+
+    /**
+     * 清除动态租户
+     */
+    public static void clearDynamic() {
+        if (!SpringMVCUtil.isWeb()) {
+            TEMP_DYNAMIC_TENANT.remove();
+            return;
+        }
+        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;
+    }
+
+}

+ 33 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.tenant.manager;
+
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.manager.PlusSpringCacheManager;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+import org.springframework.cache.Cache;
+
+/**
+ * 重写 cacheName 处理方法 支持多租户
+ *
+ * @author Lion Li
+ */
+public class TenantSpringCacheManager extends PlusSpringCacheManager {
+
+    public TenantSpringCacheManager() {
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.getCache(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(name, tenantId)) {
+            // 如果存在则直接返回
+            return super.getCache(name);
+        }
+        return super.getCache(tenantId + ":" + name);
+    }
+
+}

+ 27 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java

@@ -0,0 +1,27 @@
+package com.ruoyi.common.tenant.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * 租户 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "tenant")
+public class TenantProperties {
+
+    /**
+     * 是否启用
+     */
+    private Boolean enable;
+
+    /**
+     * 排除表
+     */
+    private List<String> excludes;
+
+}

+ 1 - 0
ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.ruoyi.common.tenant.config.TenantConfig

+ 1 - 1
ruoyi-example/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/SensitiveServiceImpl.java

@@ -19,7 +19,7 @@ public class SensitiveServiceImpl implements SensitiveService {
      */
     @Override
     public boolean isSensitive() {
-        return !LoginHelper.isAdmin();
+        return !LoginHelper.isSuperAdmin();
     }
 
 }

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java

@@ -133,7 +133,7 @@ public class SysUserController extends BaseController {
         userService.checkUserDataScope(userId);
         Map<String, Object> ajax = new HashMap<>();
         List<SysRole> roles = roleService.selectRoleAll();
-        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        ajax.put("roles", LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
         ajax.put("posts", postService.selectPostAll());
         if (ObjectUtil.isNotNull(userId)) {
             SysUser sysUser = userService.selectUserById(userId);
@@ -237,7 +237,7 @@ public class SysUserController extends BaseController {
         SysUser user = userService.selectUserById(userId);
         List<SysRole> roles = roleService.selectRolesByUserId(userId);
         ajax.put("user", user);
-        ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        ajax.put("roles", LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
         return R.ok(ajax);
     }
 

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java

@@ -201,7 +201,7 @@ public class SysDeptServiceImpl implements ISysDeptService {
      */
     @Override
     public void checkDeptDataScope(Long deptId) {
-        if (!LoginHelper.isAdmin()) {
+        if (!LoginHelper.isSuperAdmin()) {
             SysDept dept = new SysDept();
             dept.setDeptId(deptId);
             List<SysDept> depts = this.selectDeptList(dept);

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java

@@ -60,7 +60,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
     public List<SysMenu> selectMenuList(SysMenu menu, Long userId) {
         List<SysMenu> menuList = null;
         // 管理员显示所有菜单信息
-        if (LoginHelper.isAdmin(userId)) {
+        if (LoginHelper.isSuperAdmin(userId)) {
             menuList = baseMapper.selectList(new LambdaQueryWrapper<SysMenu>()
                 .like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName())
                 .eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible())
@@ -125,7 +125,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
     @Override
     public List<SysMenu> selectMenuTreeByUserId(Long userId) {
         List<SysMenu> menus = null;
-        if (LoginHelper.isAdmin(userId)) {
+        if (LoginHelper.isSuperAdmin(userId)) {
             menus = baseMapper.selectMenuTreeAll();
         } else {
             menus = baseMapper.selectMenuTreeByUserId(userId);

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPermissionServiceImpl.java

@@ -33,7 +33,7 @@ public class SysPermissionServiceImpl implements ISysPermissionService {
     public Set<String> getRolePermission(SysUser user) {
         Set<String> roles = new HashSet<String>();
         // 管理员拥有所有权限
-        if (LoginHelper.isAdmin(user.getUserId())) {
+        if (LoginHelper.isSuperAdmin(user.getUserId())) {
             roles.add("admin");
         } else {
             roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
@@ -51,7 +51,7 @@ public class SysPermissionServiceImpl implements ISysPermissionService {
     public Set<String> getMenuPermission(SysUser user) {
         Set<String> perms = new HashSet<String>();
         // 管理员拥有所有权限
-        if (LoginHelper.isAdmin(user.getUserId())) {
+        if (LoginHelper.isSuperAdmin(user.getUserId())) {
             perms.add("*:*:*");
         } else {
             perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java

@@ -194,7 +194,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
      */
     @Override
     public void checkRoleDataScope(Long roleId) {
-        if (!LoginHelper.isAdmin()) {
+        if (!LoginHelper.isSuperAdmin()) {
             SysRole role = new SysRole();
             role.setRoleId(roleId);
             List<SysRole> roles = this.selectRoleList(role);

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysSensitiveServiceImpl.java

@@ -19,7 +19,7 @@ public class SysSensitiveServiceImpl implements SensitiveService {
      */
     @Override
     public boolean isSensitive() {
-        return !LoginHelper.isAdmin();
+        return !LoginHelper.isSuperAdmin();
     }
 
 }

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -254,7 +254,7 @@ public class SysUserServiceImpl implements ISysUserService {
      */
     @Override
     public void checkUserDataScope(Long userId) {
-        if (!LoginHelper.isAdmin()) {
+        if (!LoginHelper.isSuperAdmin()) {
             SysUser user = new SysUser();
             user.setUserId(userId);
             List<SysUser> users = this.selectUserList(user);