mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2025-12-30 09:52:26 +00:00
add 简单迁移租户模块,修改相关功能
This commit is contained in:
8
pom.xml
8
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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ import java.util.Set;
|
||||
public class LoginUser implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:";
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -128,5 +128,5 @@ public interface UserConstants {
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
Long ADMIN_ID = 1L;
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 isAdmin() {
|
||||
return isAdmin(getUserId());
|
||||
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 isTenantAdmin() {
|
||||
return isTenantAdmin(getLoginUser().getRolePermission());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
36
ruoyi-common/ruoyi-common-tenant/pom.xml
Normal file
36
ruoyi-common/ruoyi-common-tenant/pom.xml
Normal file
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.ruoyi.common.tenant.config.TenantConfig
|
||||
@@ -19,7 +19,7 @@ public class SensitiveServiceImpl implements SensitiveService {
|
||||
*/
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return !LoginHelper.isAdmin();
|
||||
return !LoginHelper.isSuperAdmin();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class SysSensitiveServiceImpl implements SensitiveService {
|
||||
*/
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return !LoginHelper.isAdmin();
|
||||
return !LoginHelper.isSuperAdmin();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user