Commit d67f4a7f46ce1f4a999916c001131df1b3fd9311

Authored by YunaiV
1 parent dfa09c78

文件配置的本地缓存,使用 Job 轮询,替代 MQ 广播

yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
... ... @@ -6,6 +6,9 @@ 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;
9 12  
10 13 @Mapper
11 14 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
... ... @@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; {
18 21 .orderByDesc(FileConfigDO::getId));
19 22 }
20 23  
  24 + @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
  25 + Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
  26 +
21 27 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.consumer.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
5   -import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link FileConfigRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class FileConfigRefreshConsumer extends AbstractChannelMessageListener<FileConfigRefreshMessage> {
19   -
20   - @Resource
21   - private FileConfigService fileConfigService;
22   -
23   - @Override
24   - public void onMessage(FileConfigRefreshMessage message) {
25   - log.info("[onMessage][收到 FileConfig 刷新消息]");
26   - fileConfigService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的消费者
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.consumer;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/message/file/FileConfigRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.message.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -
6   -/**
7   - * 文件配置数据刷新 Message
8   - */
9   -@Data
10   -public class FileConfigRefreshMessage extends AbstractChannelMessage {
11   -
12   - @Override
13   - public String getChannel() {
14   - return "infra.file-config.refresh";
15   - }
16   -
17   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/message/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的消息
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.message;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/file/FileConfigProducer.java deleted
1   -package cn.iocoder.yudao.module.infra.mq.producer.file;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.infra.mq.message.file.FileConfigRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * 文件配置相关消息的 Producer
11   - */
12   -@Component
13   -public class FileConfigProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link FileConfigRefreshMessage} 消息
20   - */
21   - public void sendFileConfigRefreshMessage() {
22   - FileConfigRefreshMessage message = new FileConfigRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/producer/package-info.java
1 1 /**
2   - * 占位符,避免缩进
  2 + * 消息队列的生产者
3 3 */
4 4 package cn.iocoder.yudao.module.infra.mq.producer;
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
... ... @@ -17,11 +17,6 @@ import javax.validation.Valid;
17 17 public interface FileConfigService {
18 18  
19 19 /**
20   - * 初始化文件客户端
21   - */
22   - void initLocalCache();
23   -
24   - /**
25 20 * 创建文件配置
26 21 *
27 22 * @param createReqVO 创建信息
... ...
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;
3 4 import cn.hutool.core.io.resource.ResourceUtil;
4 5 import cn.hutool.core.util.IdUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 9 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
8 10 import cn.iocoder.yudao.framework.file.core.client.FileClient;
... ... @@ -15,20 +17,20 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
15 17 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
16 18 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
17 19 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
18   -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
19 20 import lombok.Getter;
20 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.scheduling.annotation.Scheduled;
21 23 import org.springframework.stereotype.Service;
22 24 import org.springframework.transaction.annotation.Transactional;
23   -import org.springframework.transaction.support.TransactionSynchronization;
24   -import org.springframework.transaction.support.TransactionSynchronizationManager;
25 25 import org.springframework.validation.annotation.Validated;
26 26  
27 27 import javax.annotation.PostConstruct;
28 28 import javax.annotation.Resource;
29 29 import javax.validation.Validator;
  30 +import java.time.LocalDateTime;
30 31 import java.util.List;
31 32 import java.util.Map;
  33 +import java.util.concurrent.TimeUnit;
32 34  
33 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 36 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
... ... @@ -46,6 +48,12 @@ public class FileConfigServiceImpl implements FileConfigService {
46 48  
47 49 @Resource
48 50 private FileClientFactory fileClientFactory;
  51 +
  52 + /**
  53 + * 文件配置的缓存
  54 + */
  55 + @Getter
  56 + private List<FileConfigDO> fileConfigCache;
49 57 /**
50 58 * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
51 59 */
... ... @@ -56,9 +64,6 @@ public class FileConfigServiceImpl implements FileConfigService {
56 64 private FileConfigMapper fileConfigMapper;
57 65  
58 66 @Resource
59   - private FileConfigProducer fileConfigProducer;
60   -
61   - @Resource
62 67 private Validator validator;
63 68  
64 69 @Override
... ... @@ -76,6 +81,27 @@ public class FileConfigServiceImpl implements FileConfigService {
76 81 masterFileClient = fileClientFactory.getFileClient(config.getId());
77 82 }
78 83 });
  84 + this.fileConfigCache = configs;
  85 + }
  86 +
  87 + /**
  88 + * 通过定时任务轮询,刷新缓存
  89 + *
  90 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  91 + */
  92 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  93 + public void refreshLocalCache() {
  94 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  95 + if (CollUtil.isEmpty(fileConfigCache)) {
  96 + initLocalCache();
  97 + return;
  98 + }
  99 +
  100 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  101 + LocalDateTime maxTime = CollectionUtils.getMaxValue(fileConfigCache, FileConfigDO::getUpdateTime);
  102 + if (fileConfigMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  103 + initLocalCache();
  104 + }
79 105 }
80 106  
81 107 @Override
... ... @@ -85,9 +111,9 @@ public class FileConfigServiceImpl implements FileConfigService {
85 111 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
86 112 .setMaster(false); // 默认非 master
87 113 fileConfigMapper.insert(fileConfig);
88   - // 发送刷新配置的消息
89   - fileConfigProducer.sendFileConfigRefreshMessage();
90   - // 返回
  114 +
  115 + // 刷新缓存
  116 + initLocalCache();
91 117 return fileConfig.getId();
92 118 }
93 119  
... ... @@ -99,8 +125,9 @@ public class FileConfigServiceImpl implements FileConfigService {
99 125 FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
100 126 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
101 127 fileConfigMapper.updateById(updateObj);
102   - // 发送刷新配置的消息
103   - fileConfigProducer.sendFileConfigRefreshMessage();
  128 +
  129 + // 刷新缓存
  130 + initLocalCache();
104 131 }
105 132  
106 133 @Override
... ... @@ -112,15 +139,9 @@ public class FileConfigServiceImpl implements FileConfigService {
112 139 fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
113 140 // 更新
114 141 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
115   - // 发送刷新配置的消息
116   - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
117 142  
118   - @Override
119   - public void afterCommit() {
120   - fileConfigProducer.sendFileConfigRefreshMessage();
121   - }
122   -
123   - });
  143 + // 刷新缓存
  144 + initLocalCache();
124 145 }
125 146  
126 147 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
... ... @@ -143,8 +164,9 @@ public class FileConfigServiceImpl implements FileConfigService {
143 164 }
144 165 // 删除
145 166 fileConfigMapper.deleteById(id);
146   - // 发送刷新配置的消息
147   - fileConfigProducer.sendFileConfigRefreshMessage();
  167 +
  168 + // 刷新缓存
  169 + initLocalCache();
148 170 }
149 171  
150 172 private FileConfigDO validateFileConfigExists(Long id) {
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
... ... @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigP
16 16 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
17 17 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
18 18 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
19   -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
20 19 import lombok.Data;
21 20 import org.junit.jupiter.api.Test;
22 21 import org.springframework.boot.test.mock.mockito.MockBean;
... ... @@ -56,8 +55,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
56 55 private FileConfigMapper fileConfigMapper;
57 56  
58 57 @MockBean
59   - private FileConfigProducer fileConfigProducer;
60   - @MockBean
61 58 private Validator validator;
62 59 @MockBean
63 60 private FileClientFactory fileClientFactory;
... ... @@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
81 78 verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
82 79 eq(configDO2.getStorage()), eq(configDO2.getConfig()));
83 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));
84 85 }
85 86  
86 87 @Test
... ... @@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
101 102 assertFalse(fileConfig.getMaster());
102 103 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
103 104 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
104   - // verify 调用
105   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
106 105 }
107 106  
108 107 @Test
... ... @@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
126 125 assertPojoEquals(reqVO, fileConfig, "config");
127 126 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
128 127 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
129   - // verify 调用
130   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
131 128 }
132 129  
133 130 @Test
... ... @@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
152 149 // 断言数据
153 150 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
154 151 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
155   - // verify 调用
156   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
157 152 }
158 153  
159 154 @Test
... ... @@ -174,8 +169,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
174 169 fileConfigService.deleteFileConfig(id);
175 170 // 校验数据不存在了
176 171 assertNull(fileConfigMapper.selectById(id));
177   - // verify 调用
178   - verify(fileConfigProducer).sendFileConfigRefreshMessage();
179 172 }
180 173  
181 174 @Test
... ...