From 5e4e7ba73acc644dc99aeade08145f9c406367a8 Mon Sep 17 00:00:00 2001 From: Yjoioooo <693337446@qq.com> Date: Mon, 24 Apr 2023 10:19:16 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E7=AE=80=E5=8D=95=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 + .../com/ruoyi/system/api/domain/SysRole.java | 3 +- .../com/ruoyi/system/api/domain/SysUser.java | 2 +- .../com/ruoyi/system/api/model/LoginUser.java | 5 + ruoyi-common/pom.xml | 1 + ruoyi-common/ruoyi-common-bom/pom.xml | 7 + .../common/core/constant/GlobalConstants.java | 39 +++++ .../common/core/constant/TenantConstants.java | 45 ++++++ .../common/core/constant/UserConstants.java | 2 +- .../handler/PlusDataPermissionHandler.java | 2 +- ruoyi-common/ruoyi-common-satoken/pom.xml | 6 + .../common/satoken/utils/LoginHelper.java | 50 +++++- ruoyi-common/ruoyi-common-tenant/pom.xml | 36 +++++ .../common/tenant/config/TenantConfig.java | 100 ++++++++++++ .../common/tenant/core/TenantEntity.java | 21 +++ .../common/tenant/core/TenantSaTokenDao.java | 149 ++++++++++++++++++ .../tenant/exception/TenantException.java | 18 +++ .../tenant/handle/PlusTenantLineHandler.java | 58 +++++++ .../tenant/handle/TenantKeyPrefixHandler.java | 59 +++++++ .../common/tenant/helper/TenantHelper.java | 110 +++++++++++++ .../manager/TenantSpringCacheManager.java | 33 ++++ .../tenant/properties/TenantProperties.java | 27 ++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../service/impl/SensitiveServiceImpl.java | 2 +- .../system/controller/SysUserController.java | 4 +- .../service/impl/SysDeptServiceImpl.java | 2 +- .../service/impl/SysMenuServiceImpl.java | 4 +- .../impl/SysPermissionServiceImpl.java | 4 +- .../service/impl/SysRoleServiceImpl.java | 2 +- .../service/impl/SysSensitiveServiceImpl.java | 2 +- .../service/impl/SysUserServiceImpl.java | 2 +- 31 files changed, 782 insertions(+), 22 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java create mode 100644 ruoyi-common/ruoyi-common-tenant/pom.xml create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java create mode 100644 ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/pom.xml b/pom.xml index 06bb0a7d..d853b2c8 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ 7.14.0 8.14.0 1.72 + 2.14.2 2.7.0 @@ -357,6 +358,13 @@ ${fastjson.version} + + com.alibaba + transmittable-thread-local + ${alibaba-ttl.version} + + + diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java index d9e5fecd..fc8eba43 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java +++ b/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); } } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java index 6ffbd9b5..4b950697 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java +++ b/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); } } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUser.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUser.java index ed966399..f96ae569 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUser.java +++ b/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 */ diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index d8938346..f9df07ad 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -36,6 +36,7 @@ ruoyi-common-prometheus ruoyi-common-translation ruoyi-common-encrypt + ruoyi-common-tenant ruoyi-common diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 28525878..b4920bde 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -174,6 +174,13 @@ ${project.version} + + + com.ruoyi + ruoyi-common-tenant + ${revision} + + diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java new file mode 100644 index 00000000..7fcdad11 --- /dev/null +++ b/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:"; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java new file mode 100644 index 00000000..6411ba41 --- /dev/null +++ b/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"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java index a02ba4f2..c803cf03 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java +++ b/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; } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java index 5a82331d..d73f1b25 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java +++ b/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); diff --git a/ruoyi-common/ruoyi-common-satoken/pom.xml b/ruoyi-common/ruoyi-common-satoken/pom.xml index 89808306..1dde19cd 100644 --- a/ruoyi-common/ruoyi-common-satoken/pom.xml +++ b/ruoyi-common/ruoyi-common-satoken/pom.xml @@ -52,6 +52,12 @@ ruoyi-common-redis + + + cn.dev33 + sa-token-spring-boot-starter + + diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java index fcca599d..2641e829 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java +++ b/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; + /** * 登录鉴权助手 *

@@ -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 rolePermission) { + return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY); } - public static boolean isAdmin() { - return isAdmin(getUserId()); + public static boolean isTenantAdmin() { + return isTenantAdmin(getLoginUser().getRolePermission()); } } diff --git a/ruoyi-common/ruoyi-common-tenant/pom.xml b/ruoyi-common/ruoyi-common-tenant/pom.xml new file mode 100644 index 00000000..3265e25c --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/pom.xml @@ -0,0 +1,36 @@ + + + + com.ruoyi + ruoyi-common + 1.6.0 + + 4.0.0 + + ruoyi-common-tenant + + + ruoyi-common-tenant 租户模块 + + + + + com.ruoyi + ruoyi-common-mybatis + + + + com.ruoyi + ruoyi-common-redis + + + + com.alibaba + transmittable-thread-local + + + + + diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java new file mode 100644 index 00000000..dd94d826 --- /dev/null +++ b/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 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(); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java new file mode 100644 index 00000000..0cef7c33 --- /dev/null +++ b/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; + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java new file mode 100644 index 00000000..fd9fefad --- /dev/null +++ b/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 searchData(String prefix, String keyword, int start, int size, boolean sortType) { + return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java new file mode 100644 index 00000000..b1f6ffa7 --- /dev/null +++ b/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); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java new file mode 100644 index 00000000..6c7002df --- /dev/null +++ b/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 excludes = tenantProperties.getExcludes(); + // 非业务表 + excludes.addAll(Arrays.asList( + "gen_table", + "gen_table_column" + )); + return excludes.contains(tableName); + } + return true; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java new file mode 100644 index 00000000..98119cd2 --- /dev/null +++ b/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; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java new file mode 100644 index 00000000..7f762aa5 --- /dev/null +++ b/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 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(); + } + + /** + * 设置动态租户(一直有效 需要手动清理) + *

+ * 如果为非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); + } + + /** + * 获取动态租户(一直有效 需要手动清理) + *

+ * 如果为非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; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java new file mode 100644 index 00000000..331af757 --- /dev/null +++ b/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); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java new file mode 100644 index 00000000..473ea77b --- /dev/null +++ b/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 excludes; + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..8f39d11f --- /dev/null +++ b/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 diff --git a/ruoyi-example/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/SensitiveServiceImpl.java b/ruoyi-example/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/SensitiveServiceImpl.java index ff48be7d..b10f1514 100644 --- a/ruoyi-example/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/SensitiveServiceImpl.java +++ b/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(); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java index ed1327e4..2e12df07 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysUserController.java +++ b/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 ajax = new HashMap<>(); List 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 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); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java index 34969330..625b12a4 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java +++ b/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 depts = this.selectDeptList(dept); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java index 5e00ac3d..95860b41 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java +++ b/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 selectMenuList(SysMenu menu, Long userId) { List menuList = null; // 管理员显示所有菜单信息 - if (LoginHelper.isAdmin(userId)) { + if (LoginHelper.isSuperAdmin(userId)) { menuList = baseMapper.selectList(new LambdaQueryWrapper() .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 selectMenuTreeByUserId(Long userId) { List menus = null; - if (LoginHelper.isAdmin(userId)) { + if (LoginHelper.isSuperAdmin(userId)) { menus = baseMapper.selectMenuTreeAll(); } else { menus = baseMapper.selectMenuTreeByUserId(userId); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPermissionServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPermissionServiceImpl.java index 3b650ecf..549277fb 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPermissionServiceImpl.java +++ b/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 getRolePermission(SysUser user) { Set roles = new HashSet(); // 管理员拥有所有权限 - 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 getMenuPermission(SysUser user) { Set perms = new HashSet(); // 管理员拥有所有权限 - if (LoginHelper.isAdmin(user.getUserId())) { + if (LoginHelper.isSuperAdmin(user.getUserId())) { perms.add("*:*:*"); } else { perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java index 9c29ea49..4d5b270d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java +++ b/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 roles = this.selectRoleList(role); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysSensitiveServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysSensitiveServiceImpl.java index b4c3f29a..a1749343 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysSensitiveServiceImpl.java +++ b/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(); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java index 76c45f89..7c9e32e5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/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 users = this.selectUserList(user);