Przeglądaj źródła

基于 Guava 实现 tenant 租户数据的本地缓存

YunaiV 2 lat temu
rodzic
commit
992e20530d

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml

@@ -56,6 +56,12 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 47 - 2
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java

@@ -1,8 +1,14 @@
 package cn.iocoder.yudao.framework.tenant.core.service;
 
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
 
+import java.time.Duration;
 import java.util.List;
 
 /**
@@ -13,16 +19,55 @@ import java.util.List;
 @RequiredArgsConstructor
 public class TenantFrameworkServiceImpl implements TenantFrameworkService {
 
+    private static final ServiceException SERVICE_EXCEPTION_NULL = new ServiceException();
+
     private final TenantApi tenantApi;
 
+    /**
+     * 针对 {@link #getTenantIds()} 的缓存
+     */
+    private final LoadingCache<Object, List<Long>> getTenantIdsCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<Object, List<Long>>() {
+
+                @Override
+                public List<Long> load(Object key) {
+                    return tenantApi.getTenantIds();
+                }
+
+            });
+
+    /**
+     * 针对 {@link #validTenant(Long)} 的缓存
+     */
+    private final LoadingCache<Long, ServiceException> validTenantCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<Long, ServiceException>() {
+
+                @Override
+                public ServiceException load(Long id) {
+                    try {
+                        tenantApi.validTenant(id);
+                        return SERVICE_EXCEPTION_NULL;
+                    } catch (ServiceException ex) {
+                        return ex;
+                    }
+                }
+
+            });
+
     @Override
+    @SneakyThrows
     public List<Long> getTenantIds() {
-        return tenantApi.getTenantIds();
+        return getTenantIdsCache.get(Boolean.TRUE);
     }
 
     @Override
     public void validTenant(Long id) {
-        tenantApi.validTenant(id);
+        ServiceException serviceException = validTenantCache.getUnchecked(id);
+        if (serviceException != SERVICE_EXCEPTION_NULL) {
+            throw serviceException;
+        }
     }
 
 }

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.tenant;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
-import cn.iocoder.yudao.module.system.service.tenant.TenantService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class TenantRefreshConsumer extends AbstractChannelMessageListener<TenantRefreshMessage> {
-
-    @Resource
-    private TenantService tenantService;
-
-    @Override
-    public void onMessage(TenantRefreshMessage message) {
-        log.info("[onMessage][收到 Tenant 刷新消息]");
-        tenantService.initLocalCache();
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.tenant;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 租户数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class TenantRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.tenant.refresh";
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.tenant;
-
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
-import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Tenant 租户相关消息的 Producer
- *
- * @author 芋道源码
- */
-@Component
-public class TenantProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link RoleRefreshMessage} 消息
-     */
-    public void sendTenantRefreshMessage() {
-        TenantRefreshMessage message = new TenantRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 1 - 7
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.service.tenant;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
@@ -20,12 +19,7 @@ import java.util.Set;
  *
  * @author 芋道源码
  */
-public interface TenantService extends TenantFrameworkService {
-
-    /**
-     * 初始化租户的本地缓存
-     */
-    void initLocalCache();
+public interface TenantService {
 
     /**
      * 创建租户

+ 6 - 92
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -23,31 +23,25 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
 import cn.iocoder.yudao.module.system.service.permission.MenuService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.util.*;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertImmutableMap;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static java.util.Collections.singleton;
 
@@ -61,26 +55,6 @@ import static java.util.Collections.singleton;
 @Slf4j
 public class TenantServiceImpl implements TenantService {
 
-    /**
-     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
-     * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
-     */
-    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
-    /**
-     * 角色缓存
-     * key:角色编号 {@link RoleDO#getId()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Map<Long, TenantDO> tenantCache;
-    /**
-     * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
-     */
-    @Getter
-    private volatile Date maxUpdateTime;
-
     @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
     @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
     private TenantProperties tenantProperties;
@@ -100,61 +74,15 @@ public class TenantServiceImpl implements TenantService {
     @Resource
     private PermissionService permissionService;
 
-    @Resource
-    private TenantProducer tenantProducer;
-
-    /**
-     * 初始化 {@link #tenantCache} 缓存
-     */
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 获取租户列表,如果有更新
-        List<TenantDO> tenantList = loadTenantIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(tenantList)) {
-            return;
-        }
-
-        // 写入缓存
-        tenantCache = convertImmutableMap(tenantList, TenantDO::getId);
-        maxUpdateTime = getMaxValue(tenantList, TenantDO::getUpdateTime);
-        log.info("[initLocalCache][初始化 Tenant 数量为 {}]", tenantList.size());
-    }
-
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void schedulePeriodicRefresh() {
-        initLocalCache();
-    }
-
-    /**
-     * 如果租户发生变化,从数据库中获取最新的全量租户。
-     * 如果未发生变化,则返回空
-     *
-     * @param maxUpdateTime 当前租户的最大更新时间
-     * @return 租户列表
-     */
-    private List<TenantDO> loadTenantIfUpdate(Date maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadTenantIfUpdate][首次加载全量租户]");
-        } else { // 判断数据库中是否有更新的租户
-            if (tenantMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadTenantIfUpdate][增量加载全量租户]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有租户
-        return tenantMapper.selectList();
-    }
-
     @Override
     public List<Long> getTenantIds() {
-        return new ArrayList<>(tenantCache.keySet());
+        List<TenantDO> tenants = tenantMapper.selectList();
+        return CollectionUtils.convertList(tenants, TenantDO::getId);
     }
 
     @Override
     public void validTenant(Long id) {
-        TenantDO tenant = tenantCache.get(id);
+        TenantDO tenant = getTenant(id);
         if (tenant == null) {
             throw exception(TENANT_NOT_EXISTS);
         }
@@ -184,13 +112,6 @@ public class TenantServiceImpl implements TenantService {
             // 修改租户的管理员
             tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
         });
-        // 发送刷新消息
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-            @Override
-            public void afterCommit() {
-                tenantProducer.sendTenantRefreshMessage();
-            }
-        });
         return tenant.getId();
     }
 
@@ -228,13 +149,6 @@ public class TenantServiceImpl implements TenantService {
         if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {
             updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
         }
-        // 发送刷新消息
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-            @Override
-            public void afterCommit() {
-                tenantProducer.sendTenantRefreshMessage();
-            }
-        });
     }
 
     @Override

+ 1 - 37
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java

@@ -1,10 +1,10 @@
 package cn.iocoder.yudao.module.system.service.tenant;
 
-import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
@@ -16,14 +16,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
 import cn.iocoder.yudao.module.system.service.permission.MenuService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -34,13 +32,11 @@ import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@@ -78,43 +74,18 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
     private MenuService menuService;
     @MockBean
     private PermissionService permissionService;
-    @MockBean
-    private TenantProducer tenantProducer;
 
     @BeforeEach
     public void setUp() {
-        // 清理缓存
-        ReflectUtil.setFieldValue(tenantService, "tenantCache", Collections.emptyMap());
-        ReflectUtil.setFieldValue(tenantService, "maxUpdateTime", null);
         // 清理租户上下文
         TenantContextHolder.clear();
     }
 
-    @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        TenantDO tenantDO1 = randomPojo(TenantDO.class);
-        tenantMapper.insert(tenantDO1);
-        TenantDO tenantDO2 = randomPojo(TenantDO.class);
-        tenantMapper.insert(tenantDO2);
-
-        // 调用
-        tenantService.initLocalCache();
-        // 断言 tenantCache 缓存
-        Map<Long, TenantDO> tenantCache = tenantService.getTenantCache();
-        assertEquals(2, tenantCache.size());
-        assertPojoEquals(tenantDO1, tenantCache.get(tenantDO1.getId()));
-        assertPojoEquals(tenantDO2, tenantCache.get(tenantDO2.getId()));
-        // 断言 maxUpdateTime 缓存
-        assertEquals(max(tenantDO1.getUpdateTime(), tenantDO2.getUpdateTime()), tenantService.getMaxUpdateTime());
-    }
-
     @Test
     public void testGetTenantIds() {
         // mock 数据
         TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L));
         tenantMapper.insert(tenant);
-        tenantService.initLocalCache();
 
         // 调用,并断言业务异常
         List<Long> result = tenantService.getTenantIds();
@@ -131,7 +102,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         // mock 数据
         TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
         tenantMapper.insert(tenant);
-        tenantService.initLocalCache();
 
         // 调用,并断言业务异常
         assertServiceException(() -> tenantService.validTenant(1L), TENANT_DISABLE, tenant.getName());
@@ -143,7 +113,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
                 .setExpireTime(buildTime(2020, 2, 2)));
         tenantMapper.insert(tenant);
-        tenantService.initLocalCache();
 
         // 调用,并断言业务异常
         assertServiceException(() -> tenantService.validTenant(1L), TENANT_EXPIRE, tenant.getName());
@@ -155,7 +124,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
                 .setExpireTime(addTime(Duration.ofDays(1))));
         tenantMapper.insert(tenant);
-        tenantService.initLocalCache();
 
         // 调用,并断言业务异常
         tenantService.validTenant(1L);
@@ -206,8 +174,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         verify(permissionService).assignRoleMenu(eq(200L), same(tenantPackage.getMenuIds()));
         // verify 分配角色
         verify(permissionService).assignUserRole(eq(300L), eq(singleton(200L)));
-        // verify 发送刷新消息
-        verify(tenantProducer).sendTenantRefreshMessage();
     }
 
     @Test
@@ -240,8 +206,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         TenantDO tenant = tenantMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, tenant);
-        // verify 发送刷新消息
-        verify(tenantProducer).sendTenantRefreshMessage();
         // verify 设置角色权限
         verify(permissionService).assignRoleMenu(eq(100L), eq(asSet(200L, 201L)));
         verify(permissionService).assignRoleMenu(eq(101L), eq(asSet(201L)));