Commit 953d270dd90c5772d48e39c1622272db07071c0a
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 | 84 | <div>{{ parseTime(scope.row.receiveTime) }}</div> |
| 85 | 85 | </template> |
| 86 | 86 | </el-table-column> |
| 87 | - <el-table-column label="短信渠道" align="center" width="100"> | |
| 87 | + <el-table-column label="短信渠道" align="center" width="120"> | |
| 88 | 88 | <template slot-scope="scope"> |
| 89 | 89 | <div>{{ formatChannelSignature(scope.row.channelId) }}</div> |
| 90 | 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 | 68 | </el-table-column> |
| 69 | 69 | <el-table-column label="备注" align="center" prop="remark" /> |
| 70 | 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 | 72 | <template slot-scope="scope"> |
| 73 | 73 | <div>{{ formatChannelSignature(scope.row.channelId) }}</div> |
| 74 | 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 | 50 | |
| 51 | 51 | public final void refresh(SmsChannelProperties properties) { |
| 52 | 52 | // 判断是否更新 |
| 53 | - if (!properties.equals(this.properties)) { | |
| 53 | + if (properties.equals(this.properties)) { | |
| 54 | 54 | return; |
| 55 | 55 | } |
| 56 | 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 | import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; |
| 8 | 8 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 9 | 9 | import org.apache.ibatis.annotations.Mapper; |
| 10 | +import org.apache.ibatis.annotations.Select; | |
| 10 | 11 | |
| 12 | +import java.util.Date; | |
| 11 | 13 | import java.util.List; |
| 12 | 14 | |
| 13 | 15 | @Mapper |
| ... | ... | @@ -21,9 +23,7 @@ public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> { |
| 21 | 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 | import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO; |
| 8 | 8 | import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; |
| 9 | 9 | import org.apache.ibatis.annotations.Mapper; |
| 10 | +import org.apache.ibatis.annotations.Select; | |
| 10 | 11 | |
| 12 | +import java.util.Date; | |
| 11 | 13 | import java.util.List; |
| 12 | 14 | |
| 13 | 15 | @Mapper |
| ... | ... | @@ -45,4 +47,7 @@ public interface SysSmsTemplateMapper extends BaseMapperX<SysSmsTemplateDO> { |
| 45 | 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
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 | 2 | |
| 3 | 3 | import cn.iocoder.dashboard.common.core.KeyValue; |
| 4 | 4 | import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; |
| 5 | +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage; | |
| 5 | 6 | import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage; |
| 7 | +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage; | |
| 6 | 8 | import lombok.extern.slf4j.Slf4j; |
| 7 | 9 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 8 | 10 | import org.springframework.stereotype.Component; |
| ... | ... | @@ -11,7 +13,7 @@ import javax.annotation.Resource; |
| 11 | 13 | import java.util.List; |
| 12 | 14 | |
| 13 | 15 | /** |
| 14 | - * 短信发送流消息监听器 | |
| 16 | + * Sms 短信相关消息的 Producer | |
| 15 | 17 | * |
| 16 | 18 | * @author zzf |
| 17 | 19 | * @date 2021/3/9 16:35 |
| ... | ... | @@ -24,7 +26,7 @@ public class SysSmsProducer { |
| 24 | 26 | private StringRedisTemplate stringRedisTemplate; |
| 25 | 27 | |
| 26 | 28 | /** |
| 27 | - * 发送短信 Message | |
| 29 | + * 发送 {@link SysSmsSendMessage} 消息 | |
| 28 | 30 | * |
| 29 | 31 | * @param logId 短信日志编号 |
| 30 | 32 | * @param mobile 手机号 |
| ... | ... | @@ -39,4 +41,20 @@ public class SysSmsProducer { |
| 39 | 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
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
| ... | ... | @@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService { |
| 58 | 58 | */ |
| 59 | 59 | private volatile Map<Long, SysRoleDO> roleCache; |
| 60 | 60 | /** |
| 61 | - * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 | |
| 61 | + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新 | |
| 62 | 62 | */ |
| 63 | 63 | private volatile Date maxUpdateTime; |
| 64 | 64 | |
| ... | ... | @@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService { |
| 77 | 77 | @Override |
| 78 | 78 | @PostConstruct |
| 79 | 79 | public void initLocalCache() { |
| 80 | - // 获取菜单列表,如果有更新 | |
| 80 | + // 获取角色列表,如果有更新 | |
| 81 | 81 | List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime); |
| 82 | 82 | if (CollUtil.isEmpty(roleList)) { |
| 83 | 83 | return; |
| ... | ... | @@ -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 | 107 | private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) { |
| 108 | 108 | // 第一步,判断是否要更新。 |
| 109 | 109 | if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 |
| 110 | - log.info("[loadRoleIfUpdate][首次加载全量菜单]"); | |
| 111 | - } else { // 判断数据库中是否有更新的菜单 | |
| 110 | + log.info("[loadRoleIfUpdate][首次加载全量角色]"); | |
| 111 | + } else { // 判断数据库中是否有更新的角色 | |
| 112 | 112 | if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { |
| 113 | 113 | return null; |
| 114 | 114 | } |
| 115 | - log.info("[loadRoleIfUpdate][增量加载全量菜单]"); | |
| 115 | + log.info("[loadRoleIfUpdate][增量加载全量角色]"); | |
| 116 | 116 | } |
| 117 | - // 第二步,如果有更新,则从数据库加载所有菜单 | |
| 117 | + // 第二步,如果有更新,则从数据库加载所有角色 | |
| 118 | 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 | 21 | public interface SysSmsTemplateService { |
| 22 | 22 | |
| 23 | 23 | /** |
| 24 | + * 初始化短信模板的本地缓存 | |
| 25 | + */ | |
| 26 | + void initLocalCache(); | |
| 27 | + | |
| 28 | + /** | |
| 24 | 29 | * 获得短信模板 |
| 25 | 30 | * |
| 26 | 31 | * @param code 模板编码 |
| ... | ... | @@ -29,6 +34,14 @@ public interface SysSmsTemplateService { |
| 29 | 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 | 47 | * @param content 短信模板的内容 | ... | ... |
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
| 1 | 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 | 4 | import cn.iocoder.dashboard.common.pojo.PageResult; |
| 5 | +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; | |
| 5 | 6 | import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; |
| 6 | 7 | import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; |
| 7 | 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 | 11 | import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert; |
| 11 | 12 | import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; |
| 12 | 13 | import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper; |
| 14 | +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer; | |
| 13 | 15 | import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService; |
| 14 | 16 | import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; |
| 17 | +import lombok.extern.slf4j.Slf4j; | |
| 18 | +import org.springframework.scheduling.annotation.Scheduled; | |
| 15 | 19 | import org.springframework.stereotype.Service; |
| 16 | 20 | |
| 17 | 21 | import javax.annotation.PostConstruct; |
| 18 | 22 | import javax.annotation.Resource; |
| 19 | 23 | import java.util.Collection; |
| 24 | +import java.util.Comparator; | |
| 25 | +import java.util.Date; | |
| 20 | 26 | import java.util.List; |
| 21 | 27 | |
| 22 | 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 | 36 | * @date 2021/1/25 9:25 |
| 31 | 37 | */ |
| 32 | 38 | @Service |
| 39 | +@Slf4j | |
| 33 | 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 | 53 | @Resource |
| 36 | 54 | private SmsClientFactory smsClientFactory; |
| 37 | 55 | |
| ... | ... | @@ -41,23 +59,61 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { |
| 41 | 59 | @Resource |
| 42 | 60 | private SysSmsTemplateService smsTemplateService; |
| 43 | 61 | |
| 62 | + @Resource | |
| 63 | + private SysSmsProducer smsProducer; | |
| 64 | + | |
| 44 | 65 | @Override |
| 45 | 66 | @PostConstruct |
| 46 | 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 | 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 | 110 | @Override |
| 57 | 111 | public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) { |
| 58 | 112 | // 插入 |
| 59 | 113 | SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO); |
| 60 | 114 | smsChannelMapper.insert(smsChannel); |
| 115 | + // 发送刷新消息 | |
| 116 | + smsProducer.sendSmsChannelRefreshMessage(); | |
| 61 | 117 | // 返回 |
| 62 | 118 | return smsChannel.getId(); |
| 63 | 119 | } |
| ... | ... | @@ -69,6 +125,8 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { |
| 69 | 125 | // 更新 |
| 70 | 126 | SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO); |
| 71 | 127 | smsChannelMapper.updateById(updateObj); |
| 128 | + // 发送刷新消息 | |
| 129 | + smsProducer.sendSmsChannelRefreshMessage(); | |
| 72 | 130 | } |
| 73 | 131 | |
| 74 | 132 | @Override |
| ... | ... | @@ -79,8 +137,10 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService { |
| 79 | 137 | if (smsTemplateService.countByChannelId(id) > 0) { |
| 80 | 138 | throw exception(SMS_CHANNEL_HAS_CHILDREN); |
| 81 | 139 | } |
| 82 | - // 更新 | |
| 140 | + // 删除 | |
| 83 | 141 | smsChannelMapper.deleteById(id); |
| 142 | + // 发送刷新消息 | |
| 143 | + smsProducer.sendSmsChannelRefreshMessage(); | |
| 84 | 144 | } |
| 85 | 145 | |
| 86 | 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 | 98 | } |
| 99 | 99 | |
| 100 | 100 | private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) { |
| 101 | + // 获得短信模板。考虑到效率,从缓存中获取 | |
| 102 | + SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode); | |
| 101 | 103 | // 短信模板不存在 |
| 102 | - SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCode(templateCode); | |
| 103 | 104 | if (template == null) { |
| 104 | 105 | throw exception(SMS_TEMPLATE_NOT_EXISTS); |
| 105 | 106 | } | ... | ... |
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
| 1 | 1 | package cn.iocoder.dashboard.modules.system.service.sms.impl; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.ReUtil; |
| 4 | 5 | import cn.hutool.core.util.StrUtil; |
| 5 | 6 | import cn.iocoder.dashboard.common.enums.CommonStatusEnum; |
| 6 | 7 | import cn.iocoder.dashboard.common.pojo.PageResult; |
| 8 | +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; | |
| 7 | 9 | import cn.iocoder.dashboard.framework.sms.core.client.SmsClient; |
| 8 | 10 | import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; |
| 9 | 11 | import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult; |
| ... | ... | @@ -16,17 +18,19 @@ import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert; |
| 16 | 18 | import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; |
| 17 | 19 | import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; |
| 18 | 20 | import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper; |
| 21 | +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer; | |
| 19 | 22 | import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService; |
| 20 | 23 | import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; |
| 21 | 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 | 28 | import org.springframework.stereotype.Service; |
| 23 | 29 | import org.springframework.util.Assert; |
| 24 | 30 | |
| 31 | +import javax.annotation.PostConstruct; | |
| 25 | 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 | 34 | import java.util.regex.Pattern; |
| 31 | 35 | |
| 32 | 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 | 43 | * @date 2021/1/25 9:25 |
| 40 | 44 | */ |
| 41 | 45 | @Service |
| 46 | +@Slf4j | |
| 42 | 47 | public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 43 | 48 | |
| 44 | 49 | /** |
| ... | ... | @@ -46,6 +51,24 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 46 | 51 | */ |
| 47 | 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 | 72 | @Resource |
| 50 | 73 | private SysSmsTemplateMapper smsTemplateMapper; |
| 51 | 74 | |
| ... | ... | @@ -55,12 +78,67 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 55 | 78 | @Resource |
| 56 | 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 | 131 | @Override |
| 59 | 132 | public SysSmsTemplateDO getSmsTemplateByCode(String code) { |
| 60 | 133 | return smsTemplateMapper.selectByCode(code); |
| 61 | 134 | } |
| 62 | 135 | |
| 63 | 136 | @Override |
| 137 | + public SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code) { | |
| 138 | + return smsTemplateCache.get(code); | |
| 139 | + } | |
| 140 | + | |
| 141 | + @Override | |
| 64 | 142 | public String formatSmsTemplateContent(String content, Map<String, Object> params) { |
| 65 | 143 | return StrUtil.format(content, params); |
| 66 | 144 | } |
| ... | ... | @@ -84,6 +162,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 84 | 162 | template.setParams(parseTemplateContentParams(template.getContent())); |
| 85 | 163 | template.setChannelCode(channelDO.getCode()); |
| 86 | 164 | smsTemplateMapper.insert(template); |
| 165 | + // 发送刷新消息 | |
| 166 | + smsProducer.sendSmsTemplateRefreshMessage(); | |
| 87 | 167 | // 返回 |
| 88 | 168 | return template.getId(); |
| 89 | 169 | } |
| ... | ... | @@ -104,6 +184,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 104 | 184 | updateObj.setParams(parseTemplateContentParams(updateObj.getContent())); |
| 105 | 185 | updateObj.setChannelCode(channelDO.getCode()); |
| 106 | 186 | smsTemplateMapper.updateById(updateObj); |
| 187 | + // 发送刷新消息 | |
| 188 | + smsProducer.sendSmsTemplateRefreshMessage(); | |
| 107 | 189 | } |
| 108 | 190 | |
| 109 | 191 | @Override |
| ... | ... | @@ -112,6 +194,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { |
| 112 | 194 | this.validateSmsTemplateExists(id); |
| 113 | 195 | // 更新 |
| 114 | 196 | smsTemplateMapper.deleteById(id); |
| 197 | + // 发送刷新消息 | |
| 198 | + smsProducer.sendSmsTemplateRefreshMessage(); | |
| 115 | 199 | } |
| 116 | 200 | |
| 117 | 201 | private void validateSmsTemplateExists(Long id) { | ... | ... |