Commit dfa09c787ae2930c4ef7784b18abd5e085d4bb31

Authored by YunaiV
1 parent 27e70e73

短信渠道的本地缓存,使用 Job 轮询,替代 MQ 广播

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.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.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; 6 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
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 SmsChannelMapper extends BaseMapperX<SmsChannelDO> { 14 public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
@@ -18,4 +21,7 @@ public interface SmsChannelMapper extends BaseMapperX&lt;SmsChannelDO&gt; { @@ -18,4 +21,7 @@ public interface SmsChannelMapper extends BaseMapperX&lt;SmsChannelDO&gt; {
18 .orderByDesc(SmsChannelDO::getId)); 21 .orderByDesc(SmsChannelDO::getId));
19 } 22 }
20 23
  24 + @Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
  25 + Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
  26 +
21 } 27 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsChannelRefreshConsumer.java deleted
1 -package cn.iocoder.yudao.module.system.mq.consumer.sms;  
2 -  
3 -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;  
4 -import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;  
5 -import cn.iocoder.yudao.module.system.service.sms.SmsChannelService;  
6 -import lombok.extern.slf4j.Slf4j;  
7 -import org.springframework.stereotype.Component;  
8 -  
9 -import javax.annotation.Resource;  
10 -  
11 -/**  
12 - * 针对 {@link SmsChannelRefreshMessage} 的消费者  
13 - *  
14 - * @author 芋道源码  
15 - */  
16 -@Component  
17 -@Slf4j  
18 -public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener<SmsChannelRefreshMessage> {  
19 -  
20 - @Resource  
21 - private SmsChannelService smsChannelService;  
22 -  
23 - @Override  
24 - public void onMessage(SmsChannelRefreshMessage message) {  
25 - log.info("[onMessage][收到 SmsChannel 刷新消息]");  
26 - smsChannelService.initLocalCache();  
27 - }  
28 -  
29 -}  
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsChannelRefreshMessage.java deleted
1 -package cn.iocoder.yudao.module.system.mq.message.sms;  
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 SmsChannelRefreshMessage extends AbstractChannelMessage {  
15 -  
16 - @Override  
17 - public String getChannel() {  
18 - return "system.sms-channel.refresh";  
19 - }  
20 -  
21 -}  
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sms/SmsProducer.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.mq.producer.sms; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.mq.producer.sms;
2 2
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; 4 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5 -import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;  
6 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage; 5 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
7 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
8 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
@@ -24,14 +23,6 @@ public class SmsProducer { @@ -24,14 +23,6 @@ public class SmsProducer {
24 private RedisMQTemplate redisMQTemplate; 23 private RedisMQTemplate redisMQTemplate;
25 24
26 /** 25 /**
27 - * 发送 {@link SmsChannelRefreshMessage} 消息  
28 - */  
29 - public void sendSmsChannelRefreshMessage() {  
30 - SmsChannelRefreshMessage message = new SmsChannelRefreshMessage();  
31 - redisMQTemplate.send(message);  
32 - }  
33 -  
34 - /**  
35 * 发送 {@link SmsSendMessage} 消息 26 * 发送 {@link SmsSendMessage} 消息
36 * 27 *
37 * @param logId 短信日志编号 28 * @param logId 短信日志编号
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
1 package cn.iocoder.yudao.module.system.service.sms; 1 package cn.iocoder.yudao.module.system.service.sms;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; 5 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
5 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; 6 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
@@ -9,15 +10,20 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel @@ -9,15 +10,20 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
9 import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert; 10 import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert;
10 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
11 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; 12 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
12 -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer; 13 +import lombok.Getter;
13 import lombok.extern.slf4j.Slf4j; 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.scheduling.annotation.Scheduled;
14 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
15 17
16 import javax.annotation.PostConstruct; 18 import javax.annotation.PostConstruct;
17 import javax.annotation.Resource; 19 import javax.annotation.Resource;
  20 +import java.time.LocalDateTime;
  21 +import java.util.Collections;
18 import java.util.List; 22 import java.util.List;
  23 +import java.util.concurrent.TimeUnit;
19 24
20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
21 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN; 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
22 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; 28 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
23 29
@@ -30,6 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE @@ -30,6 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
30 @Slf4j 36 @Slf4j
31 public class SmsChannelServiceImpl implements SmsChannelService { 37 public class SmsChannelServiceImpl implements SmsChannelService {
32 38
  39 + /**
  40 + * 短信渠道列表的缓存
  41 + */
  42 + @Getter
  43 + private volatile List<SmsChannelDO> channelCache = Collections.emptyList();
  44 +
33 @Resource 45 @Resource
34 private SmsClientFactory smsClientFactory; 46 private SmsClientFactory smsClientFactory;
35 47
@@ -39,9 +51,6 @@ public class SmsChannelServiceImpl implements SmsChannelService { @@ -39,9 +51,6 @@ public class SmsChannelServiceImpl implements SmsChannelService {
39 @Resource 51 @Resource
40 private SmsTemplateService smsTemplateService; 52 private SmsTemplateService smsTemplateService;
41 53
42 - @Resource  
43 - private SmsProducer smsProducer;  
44 -  
45 @Override 54 @Override
46 @PostConstruct 55 @PostConstruct
47 public void initLocalCache() { 56 public void initLocalCache() {
@@ -52,6 +61,27 @@ public class SmsChannelServiceImpl implements SmsChannelService { @@ -52,6 +61,27 @@ public class SmsChannelServiceImpl implements SmsChannelService {
52 // 第二步:构建缓存:创建或更新短信 Client 61 // 第二步:构建缓存:创建或更新短信 Client
53 List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels); 62 List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
54 propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties)); 63 propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
  64 + this.channelCache = channels;
  65 + }
  66 +
  67 + /**
  68 + * 通过定时任务轮询,刷新缓存
  69 + *
  70 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  71 + */
  72 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  73 + public void refreshLocalCache() {
  74 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  75 + if (CollUtil.isEmpty(channelCache)) {
  76 + initLocalCache();
  77 + return;
  78 + }
  79 +
  80 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  81 + LocalDateTime maxTime = getMaxValue(channelCache, SmsChannelDO::getUpdateTime);
  82 + if (smsChannelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  83 + initLocalCache();
  84 + }
55 } 85 }
56 86
57 @Override 87 @Override
@@ -59,9 +89,9 @@ public class SmsChannelServiceImpl implements SmsChannelService { @@ -59,9 +89,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
59 // 插入 89 // 插入
60 SmsChannelDO smsChannel = SmsChannelConvert.INSTANCE.convert(createReqVO); 90 SmsChannelDO smsChannel = SmsChannelConvert.INSTANCE.convert(createReqVO);
61 smsChannelMapper.insert(smsChannel); 91 smsChannelMapper.insert(smsChannel);
62 - // 发送刷新消息  
63 - smsProducer.sendSmsChannelRefreshMessage();  
64 - // 返回 92 +
  93 + // 刷新缓存
  94 + initLocalCache();
65 return smsChannel.getId(); 95 return smsChannel.getId();
66 } 96 }
67 97
@@ -72,8 +102,9 @@ public class SmsChannelServiceImpl implements SmsChannelService { @@ -72,8 +102,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
72 // 更新 102 // 更新
73 SmsChannelDO updateObj = SmsChannelConvert.INSTANCE.convert(updateReqVO); 103 SmsChannelDO updateObj = SmsChannelConvert.INSTANCE.convert(updateReqVO);
74 smsChannelMapper.updateById(updateObj); 104 smsChannelMapper.updateById(updateObj);
75 - // 发送刷新消息  
76 - smsProducer.sendSmsChannelRefreshMessage(); 105 +
  106 + // 刷新缓存
  107 + initLocalCache();
77 } 108 }
78 109
79 @Override 110 @Override
@@ -86,8 +117,9 @@ public class SmsChannelServiceImpl implements SmsChannelService { @@ -86,8 +117,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
86 } 117 }
87 // 删除 118 // 删除
88 smsChannelMapper.deleteById(id); 119 smsChannelMapper.deleteById(id);
89 - // 发送刷新消息  
90 - smsProducer.sendSmsChannelRefreshMessage(); 120 +
  121 + // 刷新缓存
  122 + initLocalCache();
91 } 123 }
92 124
93 private void validateSmsChannelExists(Long id) { 125 private void validateSmsChannelExists(Long id) {
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenControllerTest.java
@@ -39,6 +39,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic @@ -39,6 +39,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
39 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 39 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
40 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; 40 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
41 import static java.util.Arrays.asList; 41 import static java.util.Arrays.asList;
  42 +import static org.hamcrest.CoreMatchers.anyOf;
  43 +import static org.hamcrest.CoreMatchers.is;
  44 +import static org.hamcrest.MatcherAssert.assertThat;
42 import static org.junit.jupiter.api.Assertions.*; 45 import static org.junit.jupiter.api.Assertions.*;
43 import static org.mockito.ArgumentMatchers.eq; 46 import static org.mockito.ArgumentMatchers.eq;
44 import static org.mockito.ArgumentMatchers.isNull; 47 import static org.mockito.ArgumentMatchers.isNull;
@@ -289,7 +292,10 @@ public class OAuth2OpenControllerTest extends BaseMockitoUnitTest { @@ -289,7 +292,10 @@ public class OAuth2OpenControllerTest extends BaseMockitoUnitTest {
289 scope, redirectUri, true, state); 292 scope, redirectUri, true, state);
290 // 断言 293 // 断言
291 assertEquals(0, result.getCode()); 294 assertEquals(0, result.getCode());
292 - assertEquals("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read", result.getData()); 295 + assertThat(result.getData(), anyOf( // 29 和 30 都有一定概率,主要是时间计算
  296 + is("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=29&scope=read"),
  297 + is("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read")
  298 + ));
293 } 299 }
294 300
295 @Test // autoApprove = false,通过 + code 301 @Test // autoApprove = false,通过 + code
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
@@ -9,13 +9,11 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel @@ -9,13 +9,11 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
9 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO; 9 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
10 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; 10 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
11 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; 11 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
12 -import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;  
13 import org.junit.jupiter.api.Test; 12 import org.junit.jupiter.api.Test;
14 import org.springframework.boot.test.mock.mockito.MockBean; 13 import org.springframework.boot.test.mock.mockito.MockBean;
15 import org.springframework.context.annotation.Import; 14 import org.springframework.context.annotation.Import;
16 15
17 import javax.annotation.Resource; 16 import javax.annotation.Resource;
18 -  
19 import java.util.List; 17 import java.util.List;
20 18
21 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; 19 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
@@ -42,8 +40,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -42,8 +40,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
42 private SmsClientFactory smsClientFactory; 40 private SmsClientFactory smsClientFactory;
43 @MockBean 41 @MockBean
44 private SmsTemplateService smsTemplateService; 42 private SmsTemplateService smsTemplateService;
45 - @MockBean  
46 - private SmsProducer smsProducer;  
47 43
48 @Test 44 @Test
49 public void testInitLocalCache_success() { 45 public void testInitLocalCache_success() {
@@ -60,6 +56,10 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -60,6 +56,10 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
60 argThat(properties -> isPojoEquals(smsChannelDO01, properties))); 56 argThat(properties -> isPojoEquals(smsChannelDO01, properties)));
61 verify(smsClientFactory, times(1)).createOrUpdateSmsClient( 57 verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
62 argThat(properties -> isPojoEquals(smsChannelDO02, properties))); 58 argThat(properties -> isPojoEquals(smsChannelDO02, properties)));
  59 + // 断言 channelCache 缓存
  60 + assertEquals(2, smsChannelService.getChannelCache().size());
  61 + assertPojoEquals(smsChannelDO01, smsChannelService.getChannelCache().get(0));
  62 + assertPojoEquals(smsChannelDO02, smsChannelService.getChannelCache().get(1));
63 } 63 }
64 64
65 @Test 65 @Test
@@ -74,8 +74,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -74,8 +74,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
74 // 校验记录的属性是否正确 74 // 校验记录的属性是否正确
75 SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId); 75 SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId);
76 assertPojoEquals(reqVO, smsChannel); 76 assertPojoEquals(reqVO, smsChannel);
77 - // 校验调用  
78 - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();  
79 } 77 }
80 78
81 @Test 79 @Test
@@ -95,8 +93,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -95,8 +93,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
95 // 校验是否更新正确 93 // 校验是否更新正确
96 SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的 94 SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的
97 assertPojoEquals(reqVO, smsChannel); 95 assertPojoEquals(reqVO, smsChannel);
98 - // 校验调用  
99 - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();  
100 } 96 }
101 97
102 @Test 98 @Test
@@ -118,10 +114,8 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -118,10 +114,8 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
118 114
119 // 调用 115 // 调用
120 smsChannelService.deleteSmsChannel(id); 116 smsChannelService.deleteSmsChannel(id);
121 - // 校验数据不存在了  
122 - assertNull(smsChannelMapper.selectById(id));  
123 - // 校验调用  
124 - verify(smsProducer, times(1)).sendSmsChannelRefreshMessage(); 117 + // 校验数据不存在了
  118 + assertNull(smsChannelMapper.selectById(id));
125 } 119 }
126 120
127 @Test 121 @Test