diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml index 1d67e7a9c..1d43ff2c5 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml @@ -56,6 +56,12 @@ spring-boot-starter-test test + + + + com.google.guava + guava + diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java index 6262f9903..75d065e1e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java +++ b/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> getTenantIdsCache = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader>() { + + @Override + public List load(Object key) { + return tenantApi.getTenantIds(); + } + + }); + + /** + * 针对 {@link #validTenant(Long)} 的缓存 + */ + private final LoadingCache validTenantCache = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 过期时间 1 分钟 + new CacheLoader() { + + @Override + public ServiceException load(Long id) { + try { + tenantApi.validTenant(id); + return SERVICE_EXCEPTION_NULL; + } catch (ServiceException ex) { + return ex; + } + } + + }); + @Override + @SneakyThrows public List 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; + } } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java deleted file mode 100644 index 5bc008ace..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java +++ /dev/null @@ -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 { - - @Resource - private TenantService tenantService; - - @Override - public void onMessage(TenantRefreshMessage message) { - log.info("[onMessage][收到 Tenant 刷新消息]"); - tenantService.initLocalCache(); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java deleted file mode 100644 index 19092a4bb..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java +++ /dev/null @@ -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"; - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java deleted file mode 100644 index eaa9f658d..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java +++ /dev/null @@ -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); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java index 19318869a..d9d55e9f4 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java +++ b/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 { /** * 创建租户 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index 160ffe824..dc4a67ef0 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/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 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 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 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 getTenantIds() { - return new ArrayList<>(tenantCache.keySet()); + List 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 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java index 37ff97fda..61daa866f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java +++ b/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 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 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)));