Commit a51579a77db99a2bde9c589a42d097878d2106bc

Authored by YunaiV
1 parent 48fd4972

fileConfig 缓存,使用 guava 替代 job 扫描,目的:提升启动速度,加快缓存失效

yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
... ... @@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
7 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9   -import org.apache.ibatis.annotations.Select;
10   -
11   -import java.time.LocalDateTime;
12 9  
13 10 @Mapper
14 11 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
... ... @@ -21,7 +18,8 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; {
21 18 .orderByDesc(FileConfigDO::getId));
22 19 }
23 20  
24   - @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
25   - Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
  21 + default FileConfigDO selectByMaster() {
  22 + return selectOne(FileConfigDO::getMaster, true);
  23 + }
26 24  
27 25 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.io.resource.ResourceUtil;
5 4 import cn.hutool.core.util.IdUtil;
6 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
10 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
... ... @@ -17,22 +15,22 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
17 15 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
18 16 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
19 17 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
  18 +import com.google.common.cache.CacheLoader;
  19 +import com.google.common.cache.LoadingCache;
20 20 import lombok.Getter;
21 21 import lombok.extern.slf4j.Slf4j;
22   -import org.springframework.scheduling.annotation.Scheduled;
23 22 import org.springframework.stereotype.Service;
24 23 import org.springframework.transaction.annotation.Transactional;
25 24 import org.springframework.validation.annotation.Validated;
26 25  
27   -import javax.annotation.PostConstruct;
28 26 import javax.annotation.Resource;
29 27 import javax.validation.Validator;
30   -import java.time.LocalDateTime;
31   -import java.util.List;
  28 +import java.time.Duration;
32 29 import java.util.Map;
33   -import java.util.concurrent.TimeUnit;
  30 +import java.util.Objects;
34 31  
35 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  33 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
36 34 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
37 35 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
38 36  
... ... @@ -46,19 +44,29 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG
46 44 @Slf4j
47 45 public class FileConfigServiceImpl implements FileConfigService {
48 46  
49   - @Resource
50   - private FileClientFactory fileClientFactory;
  47 + private static final Long CACHE_MASTER_ID = 0L;
51 48  
52 49 /**
53   - * 文件配置的缓存
54   - */
55   - @Getter
56   - private List<FileConfigDO> fileConfigCache;
57   - /**
58   - * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
  50 + * {@link FileClient} 缓存,通过它异步刷新 fileClientFactory
59 51 */
60 52 @Getter
61   - private FileClient masterFileClient;
  53 + private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
  54 + new CacheLoader<Long, FileClient>() {
  55 +
  56 + @Override
  57 + public FileClient load(Long id) {
  58 + FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
  59 + fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
  60 + if (config != null) {
  61 + fileClientFactory.createOrUpdateFileClient(id, config.getStorage(), config.getConfig());
  62 + }
  63 + return fileClientFactory.getFileClient(id);
  64 + }
  65 +
  66 + });
  67 +
  68 + @Resource
  69 + private FileClientFactory fileClientFactory;
62 70  
63 71 @Resource
64 72 private FileConfigMapper fileConfigMapper;
... ... @@ -66,53 +74,12 @@ public class FileConfigServiceImpl implements FileConfigService {
66 74 @Resource
67 75 private Validator validator;
68 76  
69   - @PostConstruct
70   - public void initLocalCache() {
71   - // 第一步:查询数据
72   - List<FileConfigDO> configs = fileConfigMapper.selectList();
73   - log.info("[initLocalCache][缓存文件配置,数量为:{}]", configs.size());
74   -
75   - // 第二步:构建缓存:创建或更新文件 Client
76   - configs.forEach(config -> {
77   - fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
78   - // 如果是 master,进行设置
79   - if (Boolean.TRUE.equals(config.getMaster())) {
80   - masterFileClient = fileClientFactory.getFileClient(config.getId());
81   - }
82   - });
83   - this.fileConfigCache = configs;
84   - }
85   -
86   - /**
87   - * 通过定时任务轮询,刷新缓存
88   - *
89   - * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
90   - */
91   - @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
92   - public void refreshLocalCache() {
93   - // 情况一:如果缓存里没有数据,则直接刷新缓存
94   - if (CollUtil.isEmpty(fileConfigCache)) {
95   - initLocalCache();
96   - return;
97   - }
98   -
99   - // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
100   - LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
101   - if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
102   - initLocalCache();
103   - }
104   - }
105   -
106 77 @Override
107 78 public Long createFileConfig(FileConfigCreateReqVO createReqVO) {
108   - // 插入
109 79 FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)
110 80 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
111 81 .setMaster(false); // 默认非 master
112 82 fileConfigMapper.insert(fileConfig);
113   -
114   - // 刷新缓存
115   - initLocalCache();
116 83 return fileConfig.getId();
117 84 }
118 85  
... ... @@ -125,8 +92,8 @@ public class FileConfigServiceImpl implements FileConfigService {
125 92 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
126 93 fileConfigMapper.updateById(updateObj);
127 94  
128   - // 刷新缓存
129   - initLocalCache();
  95 + // 清空缓存
  96 + clearCache(config.getId(), null);
130 97 }
131 98  
132 99 @Override
... ... @@ -139,8 +106,8 @@ public class FileConfigServiceImpl implements FileConfigService {
139 106 // 更新
140 107 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
141 108  
142   - // 刷新缓存
143   - initLocalCache();
  109 + // 清空缓存
  110 + clearCache(null, true);
144 111 }
145 112  
146 113 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
... ... @@ -164,8 +131,23 @@ public class FileConfigServiceImpl implements FileConfigService {
164 131 // 删除
165 132 fileConfigMapper.deleteById(id);
166 133  
167   - // 刷新缓存
168   - initLocalCache();
  134 + // 清空缓存
  135 + clearCache(id, null);
  136 + }
  137 +
  138 + /**
  139 + * 清空指定文件配置
  140 + *
  141 + * @param id 配置编号
  142 + * @param master 是否主配置
  143 + */
  144 + private void clearCache(Long id, Boolean master) {
  145 + if (id != null) {
  146 + clientCache.invalidate(id);
  147 + }
  148 + if (Boolean.TRUE.equals(master)) {
  149 + clientCache.invalidate(CACHE_MASTER_ID);
  150 + }
169 151 }
170 152  
171 153 private FileConfigDO validateFileConfigExists(Long id) {
... ... @@ -192,12 +174,17 @@ public class FileConfigServiceImpl implements FileConfigService {
192 174 validateFileConfigExists(id);
193 175 // 上传文件
194 176 byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
195   - return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
  177 + return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
196 178 }
197 179  
198 180 @Override
199 181 public FileClient getFileClient(Long id) {
200   - return fileClientFactory.getFileClient(id);
  182 + return clientCache.getUnchecked(id);
  183 + }
  184 +
  185 + @Override
  186 + public FileClient getMasterFileClient() {
  187 + return clientCache.getUnchecked(CACHE_MASTER_ID);
201 188 }
202 189  
203 190 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
... ... @@ -60,31 +60,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
60 60 private FileClientFactory fileClientFactory;
61 61  
62 62 @Test
63   - public void testInitLocalCache() {
64   - // mock 数据
65   - FileConfigDO configDO1 = randomFileConfigDO().setId(1L).setMaster(true);
66   - fileConfigMapper.insert(configDO1);
67   - FileConfigDO configDO2 = randomFileConfigDO().setId(2L).setMaster(false);
68   - fileConfigMapper.insert(configDO2);
69   - // mock fileClientFactory 获得 master
70   - FileClient masterFileClient = mock(FileClient.class);
71   - when(fileClientFactory.getFileClient(eq(1L))).thenReturn(masterFileClient);
72   -
73   - // 调用
74   - fileConfigService.initLocalCache();
75   - // 断言 fileClientFactory 调用
76   - verify(fileClientFactory).createOrUpdateFileClient(eq(1L),
77   - eq(configDO1.getStorage()), eq(configDO1.getConfig()));
78   - verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
79   - eq(configDO2.getStorage()), eq(configDO2.getConfig()));
80   - assertSame(masterFileClient, fileConfigService.getMasterFileClient());
81   - // 断言 fileConfigCache 缓存
82   - assertEquals(2, fileConfigService.getFileConfigCache().size());
83   - assertEquals(configDO1, fileConfigService.getFileConfigCache().get(0));
84   - assertEquals(configDO2, fileConfigService.getFileConfigCache().get(1));
85   - }
86   -
87   - @Test
88 63 public void testCreateFileConfig_success() {
89 64 // 准备参数
90 65 Map<String, Object> config = MapUtil.<String, Object>builder().put("basePath", "/yunai")
... ... @@ -102,6 +77,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
102 77 assertFalse(fileConfig.getMaster());
103 78 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
104 79 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
  80 + // 验证 cache
  81 + assertNull(fileConfigService.getClientCache().getIfPresent(fileConfigId));
105 82 }
106 83  
107 84 @Test
... ... @@ -125,6 +102,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
125 102 assertPojoEquals(reqVO, fileConfig, "config");
126 103 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
127 104 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
  105 + // 验证 cache
  106 + assertNull(fileConfigService.getClientCache().getIfPresent(fileConfig.getId()));
128 107 }
129 108  
130 109 @Test
... ... @@ -149,6 +128,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
149 128 // 断言数据
150 129 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
151 130 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
  131 + // 验证 cache
  132 + assertNull(fileConfigService.getClientCache().getIfPresent(0L));
152 133 }
153 134  
154 135 @Test
... ... @@ -169,6 +150,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
169 150 fileConfigService.deleteFileConfig(id);
170 151 // 校验数据不存在了
171 152 assertNull(fileConfigMapper.selectById(id));
  153 + // 验证 cache
  154 + assertNull(fileConfigService.getClientCache().getIfPresent(id));
172 155 }
173 156  
174 157 @Test
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
... ... @@ -139,4 +139,5 @@ public class FileServiceImplTest extends BaseDbUnitTest {
139 139 // 断言
140 140 assertSame(result, content);
141 141 }
  142 +
142 143 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.service.sms;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
6 5 import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
7 6 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
8 7 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
... ... @@ -15,7 +14,6 @@ import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
15 14 import com.google.common.cache.CacheLoader;
16 15 import com.google.common.cache.LoadingCache;
17 16 import lombok.Getter;
18   -import lombok.SneakyThrows;
19 17 import lombok.extern.slf4j.Slf4j;
20 18 import org.springframework.stereotype.Service;
21 19  
... ... @@ -24,6 +22,7 @@ import java.time.Duration;
24 22 import java.util.List;
25 23  
26 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  25 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
27 26 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
28 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
29 28  
... ... @@ -40,7 +39,7 @@ public class SmsChannelServiceImpl implements SmsChannelService {
40 39 * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
41 40 */
42 41 @Getter
43   - private final LoadingCache<Long, SmsClient> idClientCache = CacheUtils.buildAsyncReloadingCache(Duration.ofSeconds(10L),
  42 + private final LoadingCache<Long, SmsClient> idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
44 43 new CacheLoader<Long, SmsClient>() {
45 44  
46 45 @Override
... ... @@ -60,7 +59,7 @@ public class SmsChannelServiceImpl implements SmsChannelService {
60 59 * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
61 60 */
62 61 @Getter
63   - private final LoadingCache<String, SmsClient> codeClientCache = CacheUtils.buildAsyncReloadingCache(Duration.ofSeconds(60L),
  62 + private final LoadingCache<String, SmsClient> codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L),
64 63 new CacheLoader<String, SmsClient>() {
65 64  
66 65 @Override
... ... @@ -87,12 +86,8 @@ public class SmsChannelServiceImpl implements SmsChannelService {
87 86  
88 87 @Override
89 88 public Long createSmsChannel(SmsChannelCreateReqVO createReqVO) {
90   - // 插入
91 89 SmsChannelDO channel = SmsChannelConvert.INSTANCE.convert(createReqVO);
92 90 smsChannelMapper.insert(channel);
93   -
94   - // 清空缓存
95   - clearCache(channel.getId(), null);
96 91 return channel.getId();
97 92 }
98 93  
... ... @@ -127,6 +122,7 @@ public class SmsChannelServiceImpl implements SmsChannelService {
127 122 * 清空指定渠道编号的缓存
128 123 *
129 124 * @param id 渠道编号
  125 + * @param code 渠道编码
130 126 */
131 127 private void clearCache(Long id, String code) {
132 128 idClientCache.invalidate(id);
... ...