Commit 953d270dd90c5772d48e39c1622272db07071c0a

Authored by YunaiV
1 parent be8b892b

增加 sms 的缓存

Showing 18 changed files with 306 additions and 32 deletions
ruoyi-ui/src/views/system/sms/smsLog.vue
@@ -84,7 +84,7 @@ @@ -84,7 +84,7 @@
84 <div>{{ parseTime(scope.row.receiveTime) }}</div> 84 <div>{{ parseTime(scope.row.receiveTime) }}</div>
85 </template> 85 </template>
86 </el-table-column> 86 </el-table-column>
87 - <el-table-column label="短信渠道" align="center" width="100"> 87 + <el-table-column label="短信渠道" align="center" width="120">
88 <template slot-scope="scope"> 88 <template slot-scope="scope">
89 <div>{{ formatChannelSignature(scope.row.channelId) }}</div> 89 <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
90 <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div> 90 <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
ruoyi-ui/src/views/system/sms/smsTemplate.vue
@@ -68,7 +68,7 @@ @@ -68,7 +68,7 @@
68 </el-table-column> 68 </el-table-column>
69 <el-table-column label="备注" align="center" prop="remark" /> 69 <el-table-column label="备注" align="center" prop="remark" />
70 <el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" /> 70 <el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" />
71 - <el-table-column label="短信渠道" align="center"> 71 + <el-table-column label="短信渠道" align="center" width="120">
72 <template slot-scope="scope"> 72 <template slot-scope="scope">
73 <div>{{ formatChannelSignature(scope.row.channelId) }}</div> 73 <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
74 <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div> 74 <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
@@ -50,7 +50,7 @@ public abstract class AbstractSmsClient implements SmsClient { @@ -50,7 +50,7 @@ public abstract class AbstractSmsClient implements SmsClient {
50 50
51 public final void refresh(SmsChannelProperties properties) { 51 public final void refresh(SmsChannelProperties properties) {
52 // 判断是否更新 52 // 判断是否更新
53 - if (!properties.equals(this.properties)) { 53 + if (properties.equals(this.properties)) {
54 return; 54 return;
55 } 55 }
56 log.info("[refresh][配置({})发生变化,重新初始化]", properties); 56 log.info("[refresh][配置({})发生变化,重新初始化]", properties);
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 @@ -7,7 +7,9 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChann
7 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; 7 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 8 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
10 11
  12 +import java.util.Date;
11 import java.util.List; 13 import java.util.List;
12 14
13 @Mapper 15 @Mapper
@@ -21,9 +23,7 @@ public interface SysSmsChannelMapper extends BaseMapperX&lt;SysSmsChannelDO&gt; { @@ -21,9 +23,7 @@ public interface SysSmsChannelMapper extends BaseMapperX&lt;SysSmsChannelDO&gt; {
21 .orderByDesc("id")); 23 .orderByDesc("id"));
22 } 24 }
23 25
24 - default List<SysSmsChannelDO> selectListByStatus(Integer status) {  
25 - return selectList(new LambdaQueryWrapper<SysSmsChannelDO>().eq(SysSmsChannelDO::getStatus, status)  
26 - .orderByAsc(SysSmsChannelDO::getId));  
27 - } 26 + @Select("SELECT id FROM sys_sms_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
  27 + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
28 28
29 } 29 }
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 @@ -7,7 +7,9 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemp
7 import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO; 7 import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
8 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; 8 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
9 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
10 11
  12 +import java.util.Date;
11 import java.util.List; 13 import java.util.List;
12 14
13 @Mapper 15 @Mapper
@@ -45,4 +47,7 @@ public interface SysSmsTemplateMapper extends BaseMapperX&lt;SysSmsTemplateDO&gt; { @@ -45,4 +47,7 @@ public interface SysSmsTemplateMapper extends BaseMapperX&lt;SysSmsTemplateDO&gt; {
45 return selectCount("channel_id", channelId); 47 return selectCount("channel_id", channelId);
46 } 48 }
47 49
  50 + @Select("SELECT id FROM sys_sms_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
  51 + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
  52 +
48 } 53 }
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsChannelRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysSmsChannelRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysSmsChannelRefreshConsumer extends AbstractChannelMessageListener<SysSmsChannelRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysSmsChannelService smsChannelService;
  22 +
  23 + @Override
  24 + public void onMessage(SysSmsChannelRefreshMessage message) {
  25 + log.info("[onMessage][收到 SmsChannel 刷新消息]");
  26 + smsChannelService.initSmsClients();
  27 + }
  28 +
  29 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsTemplateRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysSmsTemplateRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysSmsTemplateRefreshConsumer extends AbstractChannelMessageListener<SysSmsTemplateRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysSmsTemplateService smsTemplateService;
  22 +
  23 + @Override
  24 + public void onMessage(SysSmsTemplateRefreshMessage message) {
  25 + log.info("[onMessage][收到 SmsTemplate 刷新消息]");
  26 + smsTemplateService.initLocalCache();
  27 + }
  28 +
  29 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsChannelRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 短信渠道的数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysSmsChannelRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.sms-channel.refresh";
  15 + }
  16 +
  17 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsTemplateRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 短信模板的数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysSmsTemplateRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.sms-template.refresh";
  15 + }
  16 +
  17 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java
@@ -9,6 +9,8 @@ import javax.annotation.Resource; @@ -9,6 +9,8 @@ import javax.annotation.Resource;
9 9
10 /** 10 /**
11 * Role 角色相关消息的 Producer 11 * Role 角色相关消息的 Producer
  12 + *
  13 + * @author 芋道源码
12 */ 14 */
13 @Component 15 @Component
14 public class SysRoleProducer { 16 public class SysRoleProducer {
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; @@ -2,7 +2,9 @@ package cn.iocoder.dashboard.modules.system.mq.producer.sms;
2 2
3 import cn.iocoder.dashboard.common.core.KeyValue; 3 import cn.iocoder.dashboard.common.core.KeyValue;
4 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; 4 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  5 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
5 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage; 6 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  7 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
6 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
7 import org.springframework.data.redis.core.StringRedisTemplate; 9 import org.springframework.data.redis.core.StringRedisTemplate;
8 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
@@ -11,7 +13,7 @@ import javax.annotation.Resource; @@ -11,7 +13,7 @@ import javax.annotation.Resource;
11 import java.util.List; 13 import java.util.List;
12 14
13 /** 15 /**
14 - * 短信发送流消息监听器 16 + * Sms 短信相关消息的 Producer
15 * 17 *
16 * @author zzf 18 * @author zzf
17 * @date 2021/3/9 16:35 19 * @date 2021/3/9 16:35
@@ -24,7 +26,7 @@ public class SysSmsProducer { @@ -24,7 +26,7 @@ public class SysSmsProducer {
24 private StringRedisTemplate stringRedisTemplate; 26 private StringRedisTemplate stringRedisTemplate;
25 27
26 /** 28 /**
27 - * 发送短信 Message 29 + * 发送 {@link SysSmsSendMessage} 消息
28 * 30 *
29 * @param logId 短信日志编号 31 * @param logId 短信日志编号
30 * @param mobile 手机号 32 * @param mobile 手机号
@@ -39,4 +41,20 @@ public class SysSmsProducer { @@ -39,4 +41,20 @@ public class SysSmsProducer {
39 RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message); 41 RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message);
40 } 42 }
41 43
  44 + /**
  45 + * 发送 {@link SysSmsChannelRefreshMessage} 消息
  46 + */
  47 + public void sendSmsChannelRefreshMessage() {
  48 + SysSmsChannelRefreshMessage message = new SysSmsChannelRefreshMessage();
  49 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  50 + }
  51 +
  52 + /**
  53 + * 发送 {@link SysSmsTemplateRefreshMessage} 消息
  54 + */
  55 + public void sendSmsTemplateRefreshMessage() {
  56 + SysSmsTemplateRefreshMessage message = new SysSmsTemplateRefreshMessage();
  57 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  58 + }
  59 +
42 } 60 }
src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java deleted
1 -package cn.iocoder.dashboard.modules.system.service;  
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
@@ -18,7 +18,7 @@ import java.util.Set; @@ -18,7 +18,7 @@ import java.util.Set;
18 public interface SysPermissionService extends SecurityPermissionFrameworkService { 18 public interface SysPermissionService extends SecurityPermissionFrameworkService {
19 19
20 /** 20 /**
21 - * 初始化 21 + * 初始化权限的本地缓存
22 */ 22 */
23 void initLocalCache(); 23 void initLocalCache();
24 24
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
@@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService { @@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService {
58 */ 58 */
59 private volatile Map<Long, SysRoleDO> roleCache; 59 private volatile Map<Long, SysRoleDO> roleCache;
60 /** 60 /**
61 - * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 61 + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
62 */ 62 */
63 private volatile Date maxUpdateTime; 63 private volatile Date maxUpdateTime;
64 64
@@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService { @@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService {
77 @Override 77 @Override
78 @PostConstruct 78 @PostConstruct
79 public void initLocalCache() { 79 public void initLocalCache() {
80 - // 获取菜单列表,如果有更新 80 + // 获取角色列表,如果有更新
81 List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime); 81 List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime);
82 if (CollUtil.isEmpty(roleList)) { 82 if (CollUtil.isEmpty(roleList)) {
83 return; 83 return;
@@ -98,23 +98,23 @@ public class SysRoleServiceImpl implements SysRoleService { @@ -98,23 +98,23 @@ public class SysRoleServiceImpl implements SysRoleService {
98 } 98 }
99 99
100 /** 100 /**
101 - * 如果菜单发生变化,从数据库中获取最新的全量菜单。 101 + * 如果角色发生变化,从数据库中获取最新的全量角色。
102 * 如果未发生变化,则返回空 102 * 如果未发生变化,则返回空
103 * 103 *
104 - * @param maxUpdateTime 当前菜单的最大更新时间  
105 - * @return 菜单列表 104 + * @param maxUpdateTime 当前角色的最大更新时间
  105 + * @return 角色列表
106 */ 106 */
107 private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) { 107 private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) {
108 // 第一步,判断是否要更新。 108 // 第一步,判断是否要更新。
109 if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 109 if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
110 - log.info("[loadRoleIfUpdate][首次加载全量菜单]");  
111 - } else { // 判断数据库中是否有更新的菜单 110 + log.info("[loadRoleIfUpdate][首次加载全量角色]");
  111 + } else { // 判断数据库中是否有更新的角色
112 if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { 112 if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
113 return null; 113 return null;
114 } 114 }
115 - log.info("[loadRoleIfUpdate][增量加载全量菜单]"); 115 + log.info("[loadRoleIfUpdate][增量加载全量角色]");
116 } 116 }
117 - // 第二步,如果有更新,则从数据库加载所有菜单 117 + // 第二步,如果有更新,则从数据库加载所有角色
118 return roleMapper.selectList(); 118 return roleMapper.selectList();
119 } 119 }
120 120
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java
@@ -21,6 +21,11 @@ import java.util.Map; @@ -21,6 +21,11 @@ import java.util.Map;
21 public interface SysSmsTemplateService { 21 public interface SysSmsTemplateService {
22 22
23 /** 23 /**
  24 + * 初始化短信模板的本地缓存
  25 + */
  26 + void initLocalCache();
  27 +
  28 + /**
24 * 获得短信模板 29 * 获得短信模板
25 * 30 *
26 * @param code 模板编码 31 * @param code 模板编码
@@ -29,6 +34,14 @@ public interface SysSmsTemplateService { @@ -29,6 +34,14 @@ public interface SysSmsTemplateService {
29 SysSmsTemplateDO getSmsTemplateByCode(String code); 34 SysSmsTemplateDO getSmsTemplateByCode(String code);
30 35
31 /** 36 /**
  37 + * 获得短信模板,从缓存中
  38 + *
  39 + * @param code 模板编码
  40 + * @return 短信模板
  41 + */
  42 + SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code);
  43 +
  44 + /**
32 * 格式化短信内容 45 * 格式化短信内容
33 * 46 *
34 * @param content 短信模板的内容 47 * @param content 短信模板的内容
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
1 package cn.iocoder.dashboard.modules.system.service.sms.impl; 1 package cn.iocoder.dashboard.modules.system.service.sms.impl;
2 2
3 -import cn.iocoder.dashboard.common.enums.CommonStatusEnum; 3 +import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.dashboard.common.pojo.PageResult; 4 import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
5 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; 6 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
6 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; 7 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
7 import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO; 8 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 @@ -10,13 +11,18 @@ import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChann
10 import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert; 11 import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert;
11 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; 12 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
12 import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper; 13 import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper;
  14 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
13 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService; 15 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
14 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; 16 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.scheduling.annotation.Scheduled;
15 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
16 20
17 import javax.annotation.PostConstruct; 21 import javax.annotation.PostConstruct;
18 import javax.annotation.Resource; 22 import javax.annotation.Resource;
19 import java.util.Collection; 23 import java.util.Collection;
  24 +import java.util.Comparator;
  25 +import java.util.Date;
20 import java.util.List; 26 import java.util.List;
21 27
22 import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; 28 import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
@@ -30,8 +36,20 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SM @@ -30,8 +36,20 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SM
30 * @date 2021/1/25 9:25 36 * @date 2021/1/25 9:25
31 */ 37 */
32 @Service 38 @Service
  39 +@Slf4j
33 public class SysSmsChannelServiceImpl implements SysSmsChannelService { 40 public class SysSmsChannelServiceImpl implements SysSmsChannelService {
34 41
  42 + /**
  43 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  44 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  45 + */
  46 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  47 +
  48 + /**
  49 + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
  50 + */
  51 + private volatile Date maxUpdateTime;
  52 +
35 @Resource 53 @Resource
36 private SmsClientFactory smsClientFactory; 54 private SmsClientFactory smsClientFactory;
37 55
@@ -41,23 +59,61 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { @@ -41,23 +59,61 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
41 @Resource 59 @Resource
42 private SysSmsTemplateService smsTemplateService; 60 private SysSmsTemplateService smsTemplateService;
43 61
  62 + @Resource
  63 + private SysSmsProducer smsProducer;
  64 +
44 @Override 65 @Override
45 @PostConstruct 66 @PostConstruct
46 public void initSmsClients() { 67 public void initSmsClients() {
47 - // 查询有效渠道信息  
48 - List<SysSmsChannelDO> channelDOList = smsChannelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());  
49 - // 创建渠道 Client  
50 - List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(channelDOList); 68 + // 获取短信渠道,如果有更新
  69 + List<SysSmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
  70 + if (CollUtil.isEmpty(smsChannels)) {
  71 + return;
  72 + }
  73 +
  74 + // 创建或更新短信 Client
  75 + List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(smsChannels);
51 propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties)); 76 propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
  77 +
  78 + // 写入缓存
  79 + assert smsChannels.size() > 0; // 断言,避免告警
  80 + maxUpdateTime = smsChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
  81 + log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
52 } 82 }
53 83
54 - // TODO 芋艿:刷新缓存 84 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  85 + public void schedulePeriodicRefresh() {
  86 + initSmsClients();
  87 + }
  88 +
  89 + /**
  90 + * 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
  91 + * 如果未发生变化,则返回空
  92 + *
  93 + * @param maxUpdateTime 当前短信渠道的最大更新时间
  94 + * @return 短信渠道列表
  95 + */
  96 + private List<SysSmsChannelDO> loadSmsChannelIfUpdate(Date maxUpdateTime) {
  97 + // 第一步,判断是否要更新。
  98 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  99 + log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
  100 + } else { // 判断数据库中是否有更新的短信渠道
  101 + if (smsChannelMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
  102 + return null;
  103 + }
  104 + log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
  105 + }
  106 + // 第二步,如果有更新,则从数据库加载所有短信渠道
  107 + return smsChannelMapper.selectList();
  108 + }
55 109
56 @Override 110 @Override
57 public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) { 111 public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) {
58 // 插入 112 // 插入
59 SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO); 113 SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO);
60 smsChannelMapper.insert(smsChannel); 114 smsChannelMapper.insert(smsChannel);
  115 + // 发送刷新消息
  116 + smsProducer.sendSmsChannelRefreshMessage();
61 // 返回 117 // 返回
62 return smsChannel.getId(); 118 return smsChannel.getId();
63 } 119 }
@@ -69,6 +125,8 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { @@ -69,6 +125,8 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
69 // 更新 125 // 更新
70 SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO); 126 SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO);
71 smsChannelMapper.updateById(updateObj); 127 smsChannelMapper.updateById(updateObj);
  128 + // 发送刷新消息
  129 + smsProducer.sendSmsChannelRefreshMessage();
72 } 130 }
73 131
74 @Override 132 @Override
@@ -79,8 +137,10 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { @@ -79,8 +137,10 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
79 if (smsTemplateService.countByChannelId(id) > 0) { 137 if (smsTemplateService.countByChannelId(id) > 0) {
80 throw exception(SMS_CHANNEL_HAS_CHILDREN); 138 throw exception(SMS_CHANNEL_HAS_CHILDREN);
81 } 139 }
82 - // 更新 140 + // 删除
83 smsChannelMapper.deleteById(id); 141 smsChannelMapper.deleteById(id);
  142 + // 发送刷新消息
  143 + smsProducer.sendSmsChannelRefreshMessage();
84 } 144 }
85 145
86 private void validateSmsChannelExists(Long id) { 146 private void validateSmsChannelExists(Long id) {
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
@@ -98,8 +98,9 @@ public class SysSmsServiceImpl implements SysSmsService { @@ -98,8 +98,9 @@ public class SysSmsServiceImpl implements SysSmsService {
98 } 98 }
99 99
100 private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) { 100 private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
  101 + // 获得短信模板。考虑到效率,从缓存中获取
  102 + SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
101 // 短信模板不存在 103 // 短信模板不存在
102 - SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCode(templateCode);  
103 if (template == null) { 104 if (template == null) {
104 throw exception(SMS_TEMPLATE_NOT_EXISTS); 105 throw exception(SMS_TEMPLATE_NOT_EXISTS);
105 } 106 }
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
1 package cn.iocoder.dashboard.modules.system.service.sms.impl; 1 package cn.iocoder.dashboard.modules.system.service.sms.impl;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.util.ReUtil; 4 import cn.hutool.core.util.ReUtil;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.dashboard.common.enums.CommonStatusEnum; 6 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
6 import cn.iocoder.dashboard.common.pojo.PageResult; 7 import cn.iocoder.dashboard.common.pojo.PageResult;
  8 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
7 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient; 9 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
8 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; 10 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
9 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult; 11 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
@@ -16,17 +18,19 @@ import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert; @@ -16,17 +18,19 @@ import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert;
16 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; 18 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
17 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; 19 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
18 import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper; 20 import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
  21 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
19 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService; 22 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
20 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; 23 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
21 import com.google.common.annotations.VisibleForTesting; 24 import com.google.common.annotations.VisibleForTesting;
  25 +import com.google.common.collect.ImmutableMap;
  26 +import lombok.extern.slf4j.Slf4j;
  27 +import org.springframework.scheduling.annotation.Scheduled;
22 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
23 import org.springframework.util.Assert; 29 import org.springframework.util.Assert;
24 30
  31 +import javax.annotation.PostConstruct;
25 import javax.annotation.Resource; 32 import javax.annotation.Resource;
26 -import java.util.Collection;  
27 -import java.util.List;  
28 -import java.util.Map;  
29 -import java.util.Objects; 33 +import java.util.*;
30 import java.util.regex.Pattern; 34 import java.util.regex.Pattern;
31 35
32 import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; 36 import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
@@ -39,6 +43,7 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @@ -39,6 +43,7 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
39 * @date 2021/1/25 9:25 43 * @date 2021/1/25 9:25
40 */ 44 */
41 @Service 45 @Service
  46 +@Slf4j
42 public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { 47 public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
43 48
44 /** 49 /**
@@ -46,6 +51,24 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { @@ -46,6 +51,24 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
46 */ 51 */
47 private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}"); 52 private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
48 53
  54 + /**
  55 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  56 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  57 + */
  58 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  59 +
  60 + /**
  61 + * 短信模板缓存
  62 + * key:短信模板编码 {@link SysSmsTemplateDO#getCode()}
  63 + *
  64 + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
  65 + */
  66 + private volatile Map<String, SysSmsTemplateDO> smsTemplateCache;
  67 + /**
  68 + * 缓存短信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
  69 + */
  70 + private volatile Date maxUpdateTime;
  71 +
49 @Resource 72 @Resource
50 private SysSmsTemplateMapper smsTemplateMapper; 73 private SysSmsTemplateMapper smsTemplateMapper;
51 74
@@ -55,12 +78,67 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { @@ -55,12 +78,67 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
55 @Resource 78 @Resource
56 private SmsClientFactory smsClientFactory; 79 private SmsClientFactory smsClientFactory;
57 80
  81 + @Resource
  82 + private SysSmsProducer smsProducer;
  83 +
  84 + /**
  85 + * 初始化 {@link #smsTemplateCache} 缓存
  86 + */
  87 + @Override
  88 + @PostConstruct
  89 + public void initLocalCache() {
  90 + // 获取短信模板列表,如果有更新
  91 + List<SysSmsTemplateDO> smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
  92 + if (CollUtil.isEmpty(smsTemplateList)) {
  93 + return;
  94 + }
  95 +
  96 + // 写入缓存
  97 + ImmutableMap.Builder<String, SysSmsTemplateDO> builder = ImmutableMap.builder();
  98 + smsTemplateList.forEach(sysSmsTemplateDO -> builder.put(sysSmsTemplateDO.getCode(), sysSmsTemplateDO));
  99 + smsTemplateCache = builder.build();
  100 + assert smsTemplateList.size() > 0; // 断言,避免告警
  101 + maxUpdateTime = smsTemplateList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
  102 + log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
  103 + }
  104 +
  105 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  106 + public void schedulePeriodicRefresh() {
  107 + initLocalCache();
  108 + }
  109 +
  110 + /**
  111 + * 如果短信模板发生变化,从数据库中获取最新的全量短信模板。
  112 + * 如果未发生变化,则返回空
  113 + *
  114 + * @param maxUpdateTime 当前短信模板的最大更新时间
  115 + * @return 短信模板列表
  116 + */
  117 + private List<SysSmsTemplateDO> loadSmsTemplateIfUpdate(Date maxUpdateTime) {
  118 + // 第一步,判断是否要更新。
  119 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  120 + log.info("[loadSmsTemplateIfUpdate][首次加载全量短信模板]");
  121 + } else { // 判断数据库中是否有更新的短信模板
  122 + if (smsTemplateMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
  123 + return null;
  124 + }
  125 + log.info("[loadSmsTemplateIfUpdate][增量加载全量短信模板]");
  126 + }
  127 + // 第二步,如果有更新,则从数据库加载所有短信模板
  128 + return smsTemplateMapper.selectList();
  129 + }
  130 +
58 @Override 131 @Override
59 public SysSmsTemplateDO getSmsTemplateByCode(String code) { 132 public SysSmsTemplateDO getSmsTemplateByCode(String code) {
60 return smsTemplateMapper.selectByCode(code); 133 return smsTemplateMapper.selectByCode(code);
61 } 134 }
62 135
63 @Override 136 @Override
  137 + public SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
  138 + return smsTemplateCache.get(code);
  139 + }
  140 +
  141 + @Override
64 public String formatSmsTemplateContent(String content, Map<String, Object> params) { 142 public String formatSmsTemplateContent(String content, Map<String, Object> params) {
65 return StrUtil.format(content, params); 143 return StrUtil.format(content, params);
66 } 144 }
@@ -84,6 +162,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { @@ -84,6 +162,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
84 template.setParams(parseTemplateContentParams(template.getContent())); 162 template.setParams(parseTemplateContentParams(template.getContent()));
85 template.setChannelCode(channelDO.getCode()); 163 template.setChannelCode(channelDO.getCode());
86 smsTemplateMapper.insert(template); 164 smsTemplateMapper.insert(template);
  165 + // 发送刷新消息
  166 + smsProducer.sendSmsTemplateRefreshMessage();
87 // 返回 167 // 返回
88 return template.getId(); 168 return template.getId();
89 } 169 }
@@ -104,6 +184,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { @@ -104,6 +184,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
104 updateObj.setParams(parseTemplateContentParams(updateObj.getContent())); 184 updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
105 updateObj.setChannelCode(channelDO.getCode()); 185 updateObj.setChannelCode(channelDO.getCode());
106 smsTemplateMapper.updateById(updateObj); 186 smsTemplateMapper.updateById(updateObj);
  187 + // 发送刷新消息
  188 + smsProducer.sendSmsTemplateRefreshMessage();
107 } 189 }
108 190
109 @Override 191 @Override
@@ -112,6 +194,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { @@ -112,6 +194,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
112 this.validateSmsTemplateExists(id); 194 this.validateSmsTemplateExists(id);
113 // 更新 195 // 更新
114 smsTemplateMapper.deleteById(id); 196 smsTemplateMapper.deleteById(id);
  197 + // 发送刷新消息
  198 + smsProducer.sendSmsTemplateRefreshMessage();
115 } 199 }
116 200
117 private void validateSmsTemplateExists(Long id) { 201 private void validateSmsTemplateExists(Long id) {