diff --git a/ruoyi-ui/src/views/system/sms/smsLog.vue b/ruoyi-ui/src/views/system/sms/smsLog.vue
index d24968bdb..137f04850 100644
--- a/ruoyi-ui/src/views/system/sms/smsLog.vue
+++ b/ruoyi-ui/src/views/system/sms/smsLog.vue
@@ -84,7 +84,7 @@
{{ parseTime(scope.row.receiveTime) }}
-
+
{{ formatChannelSignature(scope.row.channelId) }}
【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】
diff --git a/ruoyi-ui/src/views/system/sms/smsTemplate.vue b/ruoyi-ui/src/views/system/sms/smsTemplate.vue
index d1e0b7fbf..c65bf8838 100644
--- a/ruoyi-ui/src/views/system/sms/smsTemplate.vue
+++ b/ruoyi-ui/src/views/system/sms/smsTemplate.vue
@@ -68,7 +68,7 @@
-
+
{{ formatChannelSignature(scope.row.channelId) }}
【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】
diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
index 254040229..068c0db7c 100644
--- a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
+++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
@@ -50,7 +50,7 @@ public abstract class AbstractSmsClient implements SmsClient {
public final void refresh(SmsChannelProperties properties) {
// 判断是否更新
- if (!properties.equals(this.properties)) {
+ if (properties.equals(this.properties)) {
return;
}
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java
index 10155a1b4..69e329b90 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java
@@ -7,7 +7,9 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChann
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import java.util.Date;
import java.util.List;
@Mapper
@@ -21,9 +23,7 @@ public interface SysSmsChannelMapper extends BaseMapperX {
.orderByDesc("id"));
}
- default List selectListByStatus(Integer status) {
- return selectList(new LambdaQueryWrapper().eq(SysSmsChannelDO::getStatus, status)
- .orderByAsc(SysSmsChannelDO::getId));
- }
+ @Select("SELECT id FROM sys_sms_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
+ Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsTemplateMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsTemplateMapper.java
index 2193b2416..a41e38b2d 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsTemplateMapper.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsTemplateMapper.java
@@ -7,7 +7,9 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemp
import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import java.util.Date;
import java.util.List;
@Mapper
@@ -45,4 +47,7 @@ public interface SysSmsTemplateMapper extends BaseMapperX {
return selectCount("channel_id", channelId);
}
+ @Select("SELECT id FROM sys_sms_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
+ Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
+
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsChannelRefreshConsumer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsChannelRefreshConsumer.java
new file mode 100644
index 000000000..6105889cb
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsChannelRefreshConsumer.java
@@ -0,0 +1,29 @@
+package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
+
+import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link SysSmsChannelRefreshMessage} 的消费者
+ *
+ * @author 芋道源码
+ */
+@Component
+@Slf4j
+public class SysSmsChannelRefreshConsumer extends AbstractChannelMessageListener {
+
+ @Resource
+ private SysSmsChannelService smsChannelService;
+
+ @Override
+ public void onMessage(SysSmsChannelRefreshMessage message) {
+ log.info("[onMessage][收到 SmsChannel 刷新消息]");
+ smsChannelService.initSmsClients();
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsTemplateRefreshConsumer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsTemplateRefreshConsumer.java
new file mode 100644
index 000000000..c310c48fa
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsTemplateRefreshConsumer.java
@@ -0,0 +1,29 @@
+package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
+
+import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link SysSmsTemplateRefreshMessage} 的消费者
+ *
+ * @author 芋道源码
+ */
+@Component
+@Slf4j
+public class SysSmsTemplateRefreshConsumer extends AbstractChannelMessageListener {
+
+ @Resource
+ private SysSmsTemplateService smsTemplateService;
+
+ @Override
+ public void onMessage(SysSmsTemplateRefreshMessage message) {
+ log.info("[onMessage][收到 SmsTemplate 刷新消息]");
+ smsTemplateService.initLocalCache();
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsChannelRefreshMessage.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsChannelRefreshMessage.java
new file mode 100644
index 000000000..fa2878720
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsChannelRefreshMessage.java
@@ -0,0 +1,17 @@
+package cn.iocoder.dashboard.modules.system.mq.message.sms;
+
+import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
+import lombok.Data;
+
+/**
+ * 短信渠道的数据刷新 Message
+ */
+@Data
+public class SysSmsChannelRefreshMessage implements ChannelMessage {
+
+ @Override
+ public String getChannel() {
+ return "system.sms-channel.refresh";
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsTemplateRefreshMessage.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsTemplateRefreshMessage.java
new file mode 100644
index 000000000..4925b092a
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsTemplateRefreshMessage.java
@@ -0,0 +1,17 @@
+package cn.iocoder.dashboard.modules.system.mq.message.sms;
+
+import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
+import lombok.Data;
+
+/**
+ * 短信模板的数据刷新 Message
+ */
+@Data
+public class SysSmsTemplateRefreshMessage implements ChannelMessage {
+
+ @Override
+ public String getChannel() {
+ return "system.sms-template.refresh";
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java
index e11945dfe..b398a27a1 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java
@@ -9,6 +9,8 @@ import javax.annotation.Resource;
/**
* Role 角色相关消息的 Producer
+ *
+ * @author 芋道源码
*/
@Component
public class SysRoleProducer {
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SysSmsProducer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SysSmsProducer.java
index 8e72bcf38..d346ef02e 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SysSmsProducer.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SysSmsProducer.java
@@ -2,7 +2,9 @@ package cn.iocoder.dashboard.modules.system.mq.producer.sms;
import cn.iocoder.dashboard.common.core.KeyValue;
import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
+import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
+import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@@ -11,7 +13,7 @@ import javax.annotation.Resource;
import java.util.List;
/**
- * 短信发送流消息监听器
+ * Sms 短信相关消息的 Producer
*
* @author zzf
* @date 2021/3/9 16:35
@@ -24,7 +26,7 @@ public class SysSmsProducer {
private StringRedisTemplate stringRedisTemplate;
/**
- * 发送短信 Message
+ * 发送 {@link SysSmsSendMessage} 消息
*
* @param logId 短信日志编号
* @param mobile 手机号
@@ -39,4 +41,20 @@ public class SysSmsProducer {
RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message);
}
+ /**
+ * 发送 {@link SysSmsChannelRefreshMessage} 消息
+ */
+ public void sendSmsChannelRefreshMessage() {
+ SysSmsChannelRefreshMessage message = new SysSmsChannelRefreshMessage();
+ RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
+ }
+
+ /**
+ * 发送 {@link SysSmsTemplateRefreshMessage} 消息
+ */
+ public void sendSmsTemplateRefreshMessage() {
+ SysSmsTemplateRefreshMessage message = new SysSmsTemplateRefreshMessage();
+ RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
+ }
+
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java
deleted file mode 100644
index 09c1d9d14..000000000
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java
+++ /dev/null
@@ -1 +0,0 @@
-package cn.iocoder.dashboard.modules.system.service;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
index 5390d3294..6bb79c446 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
@@ -18,7 +18,7 @@ import java.util.Set;
public interface SysPermissionService extends SecurityPermissionFrameworkService {
/**
- * 初始化
+ * 初始化权限的本地缓存
*/
void initLocalCache();
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
index e79813636..0115905bb 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
@@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService {
*/
private volatile Map roleCache;
/**
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
+ * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile Date maxUpdateTime;
@@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
@PostConstruct
public void initLocalCache() {
- // 获取菜单列表,如果有更新
+ // 获取角色列表,如果有更新
List roleList = this.loadRoleIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(roleList)) {
return;
@@ -98,23 +98,23 @@ public class SysRoleServiceImpl implements SysRoleService {
}
/**
- * 如果菜单发生变化,从数据库中获取最新的全量菜单。
+ * 如果角色发生变化,从数据库中获取最新的全量角色。
* 如果未发生变化,则返回空
*
- * @param maxUpdateTime 当前菜单的最大更新时间
- * @return 菜单列表
+ * @param maxUpdateTime 当前角色的最大更新时间
+ * @return 角色列表
*/
private List loadRoleIfUpdate(Date maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
- log.info("[loadRoleIfUpdate][首次加载全量菜单]");
- } else { // 判断数据库中是否有更新的菜单
+ log.info("[loadRoleIfUpdate][首次加载全量角色]");
+ } else { // 判断数据库中是否有更新的角色
if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
return null;
}
- log.info("[loadRoleIfUpdate][增量加载全量菜单]");
+ log.info("[loadRoleIfUpdate][增量加载全量角色]");
}
- // 第二步,如果有更新,则从数据库加载所有菜单
+ // 第二步,如果有更新,则从数据库加载所有角色
return roleMapper.selectList();
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java
index 4402f5f48..1af5dae12 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java
@@ -20,6 +20,11 @@ import java.util.Map;
*/
public interface SysSmsTemplateService {
+ /**
+ * 初始化短信模板的本地缓存
+ */
+ void initLocalCache();
+
/**
* 获得短信模板
*
@@ -28,6 +33,14 @@ public interface SysSmsTemplateService {
*/
SysSmsTemplateDO getSmsTemplateByCode(String code);
+ /**
+ * 获得短信模板,从缓存中
+ *
+ * @param code 模板编码
+ * @return 短信模板
+ */
+ SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code);
+
/**
* 格式化短信内容
*
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
index f5d63cc64..16ecdecff 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
@@ -1,7 +1,8 @@
package cn.iocoder.dashboard.modules.system.service.sms.impl;
-import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO;
@@ -10,13 +11,18 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChann
import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper;
+import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
import java.util.List;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
@@ -30,8 +36,20 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SM
* @date 2021/1/25 9:25
*/
@Service
+@Slf4j
public class SysSmsChannelServiceImpl implements SysSmsChannelService {
+ /**
+ * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
+ * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
+ */
+ private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
+
+ /**
+ * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
+ */
+ private volatile Date maxUpdateTime;
+
@Resource
private SmsClientFactory smsClientFactory;
@@ -41,23 +59,61 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
@Resource
private SysSmsTemplateService smsTemplateService;
+ @Resource
+ private SysSmsProducer smsProducer;
+
@Override
@PostConstruct
public void initSmsClients() {
- // 查询有效渠道信息
- List channelDOList = smsChannelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
- // 创建渠道 Client
- List propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(channelDOList);
+ // 获取短信渠道,如果有更新
+ List smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
+ if (CollUtil.isEmpty(smsChannels)) {
+ return;
+ }
+
+ // 创建或更新短信 Client
+ List propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(smsChannels);
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
+
+ // 写入缓存
+ assert smsChannels.size() > 0; // 断言,避免告警
+ maxUpdateTime = smsChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+ log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
}
- // TODO 芋艿:刷新缓存
+ @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+ public void schedulePeriodicRefresh() {
+ initSmsClients();
+ }
+
+ /**
+ * 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
+ * 如果未发生变化,则返回空
+ *
+ * @param maxUpdateTime 当前短信渠道的最大更新时间
+ * @return 短信渠道列表
+ */
+ private List loadSmsChannelIfUpdate(Date maxUpdateTime) {
+ // 第一步,判断是否要更新。
+ if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
+ log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
+ } else { // 判断数据库中是否有更新的短信渠道
+ if (smsChannelMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
+ return null;
+ }
+ log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
+ }
+ // 第二步,如果有更新,则从数据库加载所有短信渠道
+ return smsChannelMapper.selectList();
+ }
@Override
public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) {
// 插入
SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO);
smsChannelMapper.insert(smsChannel);
+ // 发送刷新消息
+ smsProducer.sendSmsChannelRefreshMessage();
// 返回
return smsChannel.getId();
}
@@ -69,6 +125,8 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
// 更新
SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO);
smsChannelMapper.updateById(updateObj);
+ // 发送刷新消息
+ smsProducer.sendSmsChannelRefreshMessage();
}
@Override
@@ -79,8 +137,10 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
if (smsTemplateService.countByChannelId(id) > 0) {
throw exception(SMS_CHANNEL_HAS_CHILDREN);
}
- // 更新
+ // 删除
smsChannelMapper.deleteById(id);
+ // 发送刷新消息
+ smsProducer.sendSmsChannelRefreshMessage();
}
private void validateSmsChannelExists(Long id) {
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
index 5332c813c..cd96adedc 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
@@ -98,8 +98,9 @@ public class SysSmsServiceImpl implements SysSmsService {
}
private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
+ // 获得短信模板。考虑到效率,从缓存中获取
+ SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
// 短信模板不存在
- SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCode(templateCode);
if (template == null) {
throw exception(SMS_TEMPLATE_NOT_EXISTS);
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
index 7cd15e4ba..0a070aefb 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
@@ -1,9 +1,11 @@
package cn.iocoder.dashboard.modules.system.service.sms.impl;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
@@ -16,17 +18,19 @@ import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
+import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
+import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import java.util.regex.Pattern;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
@@ -39,6 +43,7 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
* @date 2021/1/25 9:25
*/
@Service
+@Slf4j
public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
/**
@@ -46,6 +51,24 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
*/
private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
+ /**
+ * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
+ * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
+ */
+ private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
+
+ /**
+ * 短信模板缓存
+ * key:短信模板编码 {@link SysSmsTemplateDO#getCode()}
+ *
+ * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
+ */
+ private volatile Map smsTemplateCache;
+ /**
+ * 缓存短信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
+ */
+ private volatile Date maxUpdateTime;
+
@Resource
private SysSmsTemplateMapper smsTemplateMapper;
@@ -55,11 +78,66 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
@Resource
private SmsClientFactory smsClientFactory;
+ @Resource
+ private SysSmsProducer smsProducer;
+
+ /**
+ * 初始化 {@link #smsTemplateCache} 缓存
+ */
+ @Override
+ @PostConstruct
+ public void initLocalCache() {
+ // 获取短信模板列表,如果有更新
+ List smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
+ if (CollUtil.isEmpty(smsTemplateList)) {
+ return;
+ }
+
+ // 写入缓存
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ smsTemplateList.forEach(sysSmsTemplateDO -> builder.put(sysSmsTemplateDO.getCode(), sysSmsTemplateDO));
+ smsTemplateCache = builder.build();
+ assert smsTemplateList.size() > 0; // 断言,避免告警
+ maxUpdateTime = smsTemplateList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+ log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
+ }
+
+ @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+ public void schedulePeriodicRefresh() {
+ initLocalCache();
+ }
+
+ /**
+ * 如果短信模板发生变化,从数据库中获取最新的全量短信模板。
+ * 如果未发生变化,则返回空
+ *
+ * @param maxUpdateTime 当前短信模板的最大更新时间
+ * @return 短信模板列表
+ */
+ private List loadSmsTemplateIfUpdate(Date maxUpdateTime) {
+ // 第一步,判断是否要更新。
+ if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
+ log.info("[loadSmsTemplateIfUpdate][首次加载全量短信模板]");
+ } else { // 判断数据库中是否有更新的短信模板
+ if (smsTemplateMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
+ return null;
+ }
+ log.info("[loadSmsTemplateIfUpdate][增量加载全量短信模板]");
+ }
+ // 第二步,如果有更新,则从数据库加载所有短信模板
+ return smsTemplateMapper.selectList();
+ }
+
@Override
public SysSmsTemplateDO getSmsTemplateByCode(String code) {
return smsTemplateMapper.selectByCode(code);
}
+ @Override
+ public SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
+ return smsTemplateCache.get(code);
+ }
+
@Override
public String formatSmsTemplateContent(String content, Map params) {
return StrUtil.format(content, params);
@@ -84,6 +162,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
template.setParams(parseTemplateContentParams(template.getContent()));
template.setChannelCode(channelDO.getCode());
smsTemplateMapper.insert(template);
+ // 发送刷新消息
+ smsProducer.sendSmsTemplateRefreshMessage();
// 返回
return template.getId();
}
@@ -104,6 +184,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
updateObj.setChannelCode(channelDO.getCode());
smsTemplateMapper.updateById(updateObj);
+ // 发送刷新消息
+ smsProducer.sendSmsTemplateRefreshMessage();
}
@Override
@@ -112,6 +194,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
this.validateSmsTemplateExists(id);
// 更新
smsTemplateMapper.deleteById(id);
+ // 发送刷新消息
+ smsProducer.sendSmsTemplateRefreshMessage();
}
private void validateSmsTemplateExists(Long id) {