Commit d67f4a7f46ce1f4a999916c001131df1b3fd9311
1 parent
dfa09c78
文件配置的本地缓存,使用 Job 轮询,替代 MQ 广播
Showing
10 changed files
with
56 additions
and
112 deletions
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<FileConfigDO> { |
| 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
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
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
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
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 | ... | ... |