Commit 992e20530dc6cd2db0b92ac5b43eed1a6d4c6b9a

Authored by YunaiV
1 parent d3200910

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

yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml
... ... @@ -56,6 +56,12 @@
56 56 <artifactId>spring-boot-starter-test</artifactId>
57 57 <scope>test</scope>
58 58 </dependency>
  59 +
  60 + <!-- 工具类相关 -->
  61 + <dependency>
  62 + <groupId>com.google.guava</groupId>
  63 + <artifactId>guava</artifactId>
  64 + </dependency>
59 65 </dependencies>
60 66  
61 67 </project>
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkServiceImpl.java
1 1 package cn.iocoder.yudao.framework.tenant.core.service;
2 2  
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  4 +import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
3 5 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
  6 +import com.google.common.cache.CacheLoader;
  7 +import com.google.common.cache.LoadingCache;
4 8 import lombok.RequiredArgsConstructor;
  9 +import lombok.SneakyThrows;
5 10  
  11 +import java.time.Duration;
6 12 import java.util.List;
7 13  
8 14 /**
... ... @@ -13,16 +19,55 @@ import java.util.List;
13 19 @RequiredArgsConstructor
14 20 public class TenantFrameworkServiceImpl implements TenantFrameworkService {
15 21  
  22 + private static final ServiceException SERVICE_EXCEPTION_NULL = new ServiceException();
  23 +
16 24 private final TenantApi tenantApi;
17 25  
  26 + /**
  27 + * 针对 {@link #getTenantIds()} 的缓存
  28 + */
  29 + private final LoadingCache<Object, List<Long>> getTenantIdsCache = CacheUtils.buildAsyncReloadingCache(
  30 + Duration.ofMinutes(1L), // 过期时间 1 分钟
  31 + new CacheLoader<Object, List<Long>>() {
  32 +
  33 + @Override
  34 + public List<Long> load(Object key) {
  35 + return tenantApi.getTenantIds();
  36 + }
  37 +
  38 + });
  39 +
  40 + /**
  41 + * 针对 {@link #validTenant(Long)} 的缓存
  42 + */
  43 + private final LoadingCache<Long, ServiceException> validTenantCache = CacheUtils.buildAsyncReloadingCache(
  44 + Duration.ofMinutes(1L), // 过期时间 1 分钟
  45 + new CacheLoader<Long, ServiceException>() {
  46 +
  47 + @Override
  48 + public ServiceException load(Long id) {
  49 + try {
  50 + tenantApi.validTenant(id);
  51 + return SERVICE_EXCEPTION_NULL;
  52 + } catch (ServiceException ex) {
  53 + return ex;
  54 + }
  55 + }
  56 +
  57 + });
  58 +
18 59 @Override
  60 + @SneakyThrows
19 61 public List<Long> getTenantIds() {
20   - return tenantApi.getTenantIds();
  62 + return getTenantIdsCache.get(Boolean.TRUE);
21 63 }
22 64  
23 65 @Override
24 66 public void validTenant(Long id) {
25   - tenantApi.validTenant(id);
  67 + ServiceException serviceException = validTenantCache.getUnchecked(id);
  68 + if (serviceException != SERVICE_EXCEPTION_NULL) {
  69 + throw serviceException;
  70 + }
26 71 }
27 72  
28 73 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.tenant;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.tenant.TenantService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class TenantRefreshConsumer extends AbstractChannelMessageListener<TenantRefreshMessage> {
19   -
20   - @Resource
21   - private TenantService tenantService;
22   -
23   - @Override
24   - public void onMessage(TenantRefreshMessage message) {
25   - log.info("[onMessage][收到 Tenant 刷新消息]");
26   - tenantService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.tenant;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 租户数据刷新 Message
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -@EqualsAndHashCode(callSuper = true)
14   -public class TenantRefreshMessage extends AbstractChannelMessage {
15   -
16   - @Override
17   - public String getChannel() {
18   - return "system.tenant.refresh";
19   - }
20   -
21   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.tenant;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
5   -import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
6   -import org.springframework.stereotype.Component;
7   -
8   -import javax.annotation.Resource;
9   -
10   -/**
11   - * Tenant 租户相关消息的 Producer
12   - *
13   - * @author 芋道源码
14   - */
15   -@Component
16   -public class TenantProducer {
17   -
18   - @Resource
19   - private RedisMQTemplate redisMQTemplate;
20   -
21   - /**
22   - * 发送 {@link RoleRefreshMessage} 消息
23   - */
24   - public void sendTenantRefreshMessage() {
25   - TenantRefreshMessage message = new TenantRefreshMessage();
26   - redisMQTemplate.send(message);
27   - }
28   -
29   -}
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
5   -import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
6 5 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
7 6 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
8 7 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
... ... @@ -20,12 +19,7 @@ import java.util.Set;
20 19 *
21 20 * @author 芋道源码
22 21 */
23   -public interface TenantService extends TenantFrameworkService {
24   -
25   - /**
26   - * 初始化租户的本地缓存
27   - */
28   - void initLocalCache();
  22 +public interface TenantService {
29 23  
30 24 /**
31 25 * 创建租户
... ...
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;
23 23 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
24 24 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
25 25 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
26   -import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
27 26 import cn.iocoder.yudao.module.system.service.permission.MenuService;
28 27 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
29 28 import cn.iocoder.yudao.module.system.service.permission.RoleService;
30 29 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
31 30 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
32 31 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
33   -import lombok.Getter;
34 32 import lombok.extern.slf4j.Slf4j;
35 33 import org.springframework.beans.factory.annotation.Autowired;
36 34 import org.springframework.context.annotation.Lazy;
37   -import org.springframework.scheduling.annotation.Scheduled;
38 35 import org.springframework.stereotype.Service;
39 36 import org.springframework.transaction.annotation.Transactional;
40   -import org.springframework.transaction.support.TransactionSynchronization;
41   -import org.springframework.transaction.support.TransactionSynchronizationManager;
42 37 import org.springframework.validation.annotation.Validated;
43 38  
44   -import javax.annotation.PostConstruct;
45 39 import javax.annotation.Resource;
46   -import java.util.*;
  40 +import java.util.List;
  41 +import java.util.Objects;
  42 +import java.util.Set;
47 43  
48 44 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
49   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertImmutableMap;
50   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
51 45 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
52 46 import static java.util.Collections.singleton;
53 47  
... ... @@ -61,26 +55,6 @@ import static java.util.Collections.singleton;
61 55 @Slf4j
62 56 public class TenantServiceImpl implements TenantService {
63 57  
64   - /**
65   - * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
66   - * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
67   - */
68   - private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
69   -
70   - /**
71   - * 角色缓存
72   - * key:角色编号 {@link RoleDO#getId()}
73   - *
74   - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
75   - */
76   - @Getter
77   - private volatile Map<Long, TenantDO> tenantCache;
78   - /**
79   - * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
80   - */
81   - @Getter
82   - private volatile Date maxUpdateTime;
83   -
84 58 @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
85 59 @Autowired(required = false) // 由于 yudao.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
86 60 private TenantProperties tenantProperties;
... ... @@ -100,61 +74,15 @@ public class TenantServiceImpl implements TenantService {
100 74 @Resource
101 75 private PermissionService permissionService;
102 76  
103   - @Resource
104   - private TenantProducer tenantProducer;
105   -
106   - /**
107   - * 初始化 {@link #tenantCache} 缓存
108   - */
109   - @Override
110   - @PostConstruct
111   - public void initLocalCache() {
112   - // 获取租户列表,如果有更新
113   - List<TenantDO> tenantList = loadTenantIfUpdate(maxUpdateTime);
114   - if (CollUtil.isEmpty(tenantList)) {
115   - return;
116   - }
117   -
118   - // 写入缓存
119   - tenantCache = convertImmutableMap(tenantList, TenantDO::getId);
120   - maxUpdateTime = getMaxValue(tenantList, TenantDO::getUpdateTime);
121   - log.info("[initLocalCache][初始化 Tenant 数量为 {}]", tenantList.size());
122   - }
123   -
124   - @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
125   - public void schedulePeriodicRefresh() {
126   - initLocalCache();
127   - }
128   -
129   - /**
130   - * 如果租户发生变化,从数据库中获取最新的全量租户。
131   - * 如果未发生变化,则返回空
132   - *
133   - * @param maxUpdateTime 当前租户的最大更新时间
134   - * @return 租户列表
135   - */
136   - private List<TenantDO> loadTenantIfUpdate(Date maxUpdateTime) {
137   - // 第一步,判断是否要更新。
138   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
139   - log.info("[loadTenantIfUpdate][首次加载全量租户]");
140   - } else { // 判断数据库中是否有更新的租户
141   - if (tenantMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
142   - return null;
143   - }
144   - log.info("[loadTenantIfUpdate][增量加载全量租户]");
145   - }
146   - // 第二步,如果有更新,则从数据库加载所有租户
147   - return tenantMapper.selectList();
148   - }
149   -
150 77 @Override
151 78 public List<Long> getTenantIds() {
152   - return new ArrayList<>(tenantCache.keySet());
  79 + List<TenantDO> tenants = tenantMapper.selectList();
  80 + return CollectionUtils.convertList(tenants, TenantDO::getId);
153 81 }
154 82  
155 83 @Override
156 84 public void validTenant(Long id) {
157   - TenantDO tenant = tenantCache.get(id);
  85 + TenantDO tenant = getTenant(id);
158 86 if (tenant == null) {
159 87 throw exception(TENANT_NOT_EXISTS);
160 88 }
... ... @@ -184,13 +112,6 @@ public class TenantServiceImpl implements TenantService {
184 112 // 修改租户的管理员
185 113 tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
186 114 });
187   - // 发送刷新消息
188   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
189   - @Override
190   - public void afterCommit() {
191   - tenantProducer.sendTenantRefreshMessage();
192   - }
193   - });
194 115 return tenant.getId();
195 116 }
196 117  
... ... @@ -228,13 +149,6 @@ public class TenantServiceImpl implements TenantService {
228 149 if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {
229 150 updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
230 151 }
231   - // 发送刷新消息
232   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
233   - @Override
234   - public void afterCommit() {
235   - tenantProducer.sendTenantRefreshMessage();
236   - }
237   - });
238 152 }
239 153  
240 154 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java
1 1 package cn.iocoder.yudao.module.system.service.tenant;
2 2  
3   -import cn.hutool.core.util.ReflectUtil;
4 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
7 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  7 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
8 8 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
9 9 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
10 10 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;
16 16 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
17 17 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
18 18 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
19   -import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
20 19 import cn.iocoder.yudao.module.system.service.permission.MenuService;
21 20 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
22 21 import cn.iocoder.yudao.module.system.service.permission.RoleService;
23 22 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
24 23 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
25 24 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
26   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
27 25 import org.junit.jupiter.api.BeforeEach;
28 26 import org.junit.jupiter.api.Test;
29 27 import org.springframework.boot.test.mock.mockito.MockBean;
... ... @@ -34,13 +32,11 @@ import java.time.Duration;
34 32 import java.util.Arrays;
35 33 import java.util.Collections;
36 34 import java.util.List;
37   -import java.util.Map;
38 35  
39 36 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
40 37 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
41 38 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
42 39 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
43   -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
44 40 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
45 41 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
46 42 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
... ... @@ -78,43 +74,18 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
78 74 private MenuService menuService;
79 75 @MockBean
80 76 private PermissionService permissionService;
81   - @MockBean
82   - private TenantProducer tenantProducer;
83 77  
84 78 @BeforeEach
85 79 public void setUp() {
86   - // 清理缓存
87   - ReflectUtil.setFieldValue(tenantService, "tenantCache", Collections.emptyMap());
88   - ReflectUtil.setFieldValue(tenantService, "maxUpdateTime", null);
89 80 // 清理租户上下文
90 81 TenantContextHolder.clear();
91 82 }
92 83  
93 84 @Test
94   - public void testInitLocalCache() {
95   - // mock 数据
96   - TenantDO tenantDO1 = randomPojo(TenantDO.class);
97   - tenantMapper.insert(tenantDO1);
98   - TenantDO tenantDO2 = randomPojo(TenantDO.class);
99   - tenantMapper.insert(tenantDO2);
100   -
101   - // 调用
102   - tenantService.initLocalCache();
103   - // 断言 tenantCache 缓存
104   - Map<Long, TenantDO> tenantCache = tenantService.getTenantCache();
105   - assertEquals(2, tenantCache.size());
106   - assertPojoEquals(tenantDO1, tenantCache.get(tenantDO1.getId()));
107   - assertPojoEquals(tenantDO2, tenantCache.get(tenantDO2.getId()));
108   - // 断言 maxUpdateTime 缓存
109   - assertEquals(max(tenantDO1.getUpdateTime(), tenantDO2.getUpdateTime()), tenantService.getMaxUpdateTime());
110   - }
111   -
112   - @Test
113 85 public void testGetTenantIds() {
114 86 // mock 数据
115 87 TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L));
116 88 tenantMapper.insert(tenant);
117   - tenantService.initLocalCache();
118 89  
119 90 // 调用,并断言业务异常
120 91 List<Long> result = tenantService.getTenantIds();
... ... @@ -131,7 +102,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
131 102 // mock 数据
132 103 TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
133 104 tenantMapper.insert(tenant);
134   - tenantService.initLocalCache();
135 105  
136 106 // 调用,并断言业务异常
137 107 assertServiceException(() -> tenantService.validTenant(1L), TENANT_DISABLE, tenant.getName());
... ... @@ -143,7 +113,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
143 113 TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
144 114 .setExpireTime(buildTime(2020, 2, 2)));
145 115 tenantMapper.insert(tenant);
146   - tenantService.initLocalCache();
147 116  
148 117 // 调用,并断言业务异常
149 118 assertServiceException(() -> tenantService.validTenant(1L), TENANT_EXPIRE, tenant.getName());
... ... @@ -155,7 +124,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
155 124 TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
156 125 .setExpireTime(addTime(Duration.ofDays(1))));
157 126 tenantMapper.insert(tenant);
158   - tenantService.initLocalCache();
159 127  
160 128 // 调用,并断言业务异常
161 129 tenantService.validTenant(1L);
... ... @@ -206,8 +174,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
206 174 verify(permissionService).assignRoleMenu(eq(200L), same(tenantPackage.getMenuIds()));
207 175 // verify 分配角色
208 176 verify(permissionService).assignUserRole(eq(300L), eq(singleton(200L)));
209   - // verify 发送刷新消息
210   - verify(tenantProducer).sendTenantRefreshMessage();
211 177 }
212 178  
213 179 @Test
... ... @@ -240,8 +206,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
240 206 // 校验是否更新正确
241 207 TenantDO tenant = tenantMapper.selectById(reqVO.getId()); // 获取最新的
242 208 assertPojoEquals(reqVO, tenant);
243   - // verify 发送刷新消息
244   - verify(tenantProducer).sendTenantRefreshMessage();
245 209 // verify 设置角色权限
246 210 verify(permissionService).assignRoleMenu(eq(100L), eq(asSet(200L, 201L)));
247 211 verify(permissionService).assignRoleMenu(eq(101L), eq(asSet(201L)));
... ...