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 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; 6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.time.LocalDateTime;
9 12
10 @Mapper 13 @Mapper
11 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> { 14 public interface FileConfigMapper extends BaseMapperX<FileConfigDO> {
@@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; { @@ -18,4 +21,7 @@ public interface FileConfigMapper extends BaseMapperX&lt;FileConfigDO&gt; {
18 .orderByDesc(FileConfigDO::getId)); 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 package cn.iocoder.yudao.module.infra.mq.consumer; 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 package cn.iocoder.yudao.module.infra.mq.message; 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 package cn.iocoder.yudao.module.infra.mq.producer; 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,11 +17,6 @@ import javax.validation.Valid;
17 public interface FileConfigService { 17 public interface FileConfigService {
18 18
19 /** 19 /**
20 - * 初始化文件客户端  
21 - */  
22 - void initLocalCache();  
23 -  
24 - /**  
25 * 创建文件配置 20 * 创建文件配置
26 * 21 *
27 * @param createReqVO 创建信息 22 * @param createReqVO 创建信息
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
1 package cn.iocoder.yudao.module.infra.service.file; 1 package cn.iocoder.yudao.module.infra.service.file;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.io.resource.ResourceUtil; 4 import cn.hutool.core.io.resource.ResourceUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; 9 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
8 import cn.iocoder.yudao.framework.file.core.client.FileClient; 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,20 +17,20 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
15 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert; 17 import cn.iocoder.yudao.module.infra.convert.file.FileConfigConvert;
16 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; 18 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
17 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; 19 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
18 -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;  
19 import lombok.Getter; 20 import lombok.Getter;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.scheduling.annotation.Scheduled;
21 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
22 import org.springframework.transaction.annotation.Transactional; 24 import org.springframework.transaction.annotation.Transactional;
23 -import org.springframework.transaction.support.TransactionSynchronization;  
24 -import org.springframework.transaction.support.TransactionSynchronizationManager;  
25 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
26 26
27 import javax.annotation.PostConstruct; 27 import javax.annotation.PostConstruct;
28 import javax.annotation.Resource; 28 import javax.annotation.Resource;
29 import javax.validation.Validator; 29 import javax.validation.Validator;
  30 +import java.time.LocalDateTime;
30 import java.util.List; 31 import java.util.List;
31 import java.util.Map; 32 import java.util.Map;
  33 +import java.util.concurrent.TimeUnit;
32 34
33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER; 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,6 +48,12 @@ public class FileConfigServiceImpl implements FileConfigService {
46 48
47 @Resource 49 @Resource
48 private FileClientFactory fileClientFactory; 50 private FileClientFactory fileClientFactory;
  51 +
  52 + /**
  53 + * 文件配置的缓存
  54 + */
  55 + @Getter
  56 + private List<FileConfigDO> fileConfigCache;
49 /** 57 /**
50 * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的 58 * Master FileClient 对象,有且仅有一个,即 {@link FileConfigDO#getMaster()} 对应的
51 */ 59 */
@@ -56,9 +64,6 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -56,9 +64,6 @@ public class FileConfigServiceImpl implements FileConfigService {
56 private FileConfigMapper fileConfigMapper; 64 private FileConfigMapper fileConfigMapper;
57 65
58 @Resource 66 @Resource
59 - private FileConfigProducer fileConfigProducer;  
60 -  
61 - @Resource  
62 private Validator validator; 67 private Validator validator;
63 68
64 @Override 69 @Override
@@ -76,6 +81,27 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -76,6 +81,27 @@ public class FileConfigServiceImpl implements FileConfigService {
76 masterFileClient = fileClientFactory.getFileClient(config.getId()); 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 @Override 107 @Override
@@ -85,9 +111,9 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -85,9 +111,9 @@ public class FileConfigServiceImpl implements FileConfigService {
85 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig())) 111 .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))
86 .setMaster(false); // 默认非 master 112 .setMaster(false); // 默认非 master
87 fileConfigMapper.insert(fileConfig); 113 fileConfigMapper.insert(fileConfig);
88 - // 发送刷新配置的消息  
89 - fileConfigProducer.sendFileConfigRefreshMessage();  
90 - // 返回 114 +
  115 + // 刷新缓存
  116 + initLocalCache();
91 return fileConfig.getId(); 117 return fileConfig.getId();
92 } 118 }
93 119
@@ -99,8 +125,9 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -99,8 +125,9 @@ public class FileConfigServiceImpl implements FileConfigService {
99 FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO) 125 FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
100 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig())); 126 .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
101 fileConfigMapper.updateById(updateObj); 127 fileConfigMapper.updateById(updateObj);
102 - // 发送刷新配置的消息  
103 - fileConfigProducer.sendFileConfigRefreshMessage(); 128 +
  129 + // 刷新缓存
  130 + initLocalCache();
104 } 131 }
105 132
106 @Override 133 @Override
@@ -112,15 +139,9 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -112,15 +139,9 @@ public class FileConfigServiceImpl implements FileConfigService {
112 fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false)); 139 fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
113 // 更新 140 // 更新
114 fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true)); 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 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) { 147 private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
@@ -143,8 +164,9 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -143,8 +164,9 @@ public class FileConfigServiceImpl implements FileConfigService {
143 } 164 }
144 // 删除 165 // 删除
145 fileConfigMapper.deleteById(id); 166 fileConfigMapper.deleteById(id);
146 - // 发送刷新配置的消息  
147 - fileConfigProducer.sendFileConfigRefreshMessage(); 167 +
  168 + // 刷新缓存
  169 + initLocalCache();
148 } 170 }
149 171
150 private FileConfigDO validateFileConfigExists(Long id) { 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,7 +16,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigP
16 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; 16 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO;
17 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; 17 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
18 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; 18 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
19 -import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;  
20 import lombok.Data; 19 import lombok.Data;
21 import org.junit.jupiter.api.Test; 20 import org.junit.jupiter.api.Test;
22 import org.springframework.boot.test.mock.mockito.MockBean; 21 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -56,8 +55,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -56,8 +55,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
56 private FileConfigMapper fileConfigMapper; 55 private FileConfigMapper fileConfigMapper;
57 56
58 @MockBean 57 @MockBean
59 - private FileConfigProducer fileConfigProducer;  
60 - @MockBean  
61 private Validator validator; 58 private Validator validator;
62 @MockBean 59 @MockBean
63 private FileClientFactory fileClientFactory; 60 private FileClientFactory fileClientFactory;
@@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -81,6 +78,10 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
81 verify(fileClientFactory).createOrUpdateFileClient(eq(2L), 78 verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
82 eq(configDO2.getStorage()), eq(configDO2.getConfig())); 79 eq(configDO2.getStorage()), eq(configDO2.getConfig()));
83 assertSame(masterFileClient, fileConfigService.getMasterFileClient()); 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 @Test 87 @Test
@@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -101,8 +102,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
101 assertFalse(fileConfig.getMaster()); 102 assertFalse(fileConfig.getMaster());
102 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); 103 assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
103 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); 104 assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
104 - // verify 调用  
105 - verify(fileConfigProducer).sendFileConfigRefreshMessage();  
106 } 105 }
107 106
108 @Test 107 @Test
@@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -126,8 +125,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
126 assertPojoEquals(reqVO, fileConfig, "config"); 125 assertPojoEquals(reqVO, fileConfig, "config");
127 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); 126 assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
128 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); 127 assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
129 - // verify 调用  
130 - verify(fileConfigProducer).sendFileConfigRefreshMessage();  
131 } 128 }
132 129
133 @Test 130 @Test
@@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -152,8 +149,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
152 // 断言数据 149 // 断言数据
153 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster()); 150 assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
154 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster()); 151 assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
155 - // verify 调用  
156 - verify(fileConfigProducer).sendFileConfigRefreshMessage();  
157 } 152 }
158 153
159 @Test 154 @Test
@@ -174,8 +169,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -174,8 +169,6 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
174 fileConfigService.deleteFileConfig(id); 169 fileConfigService.deleteFileConfig(id);
175 // 校验数据不存在了 170 // 校验数据不存在了
176 assertNull(fileConfigMapper.selectById(id)); 171 assertNull(fileConfigMapper.selectById(id));
177 - // verify 调用  
178 - verify(fileConfigProducer).sendFileConfigRefreshMessage();  
179 } 172 }
180 173
181 @Test 174 @Test