mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	增加 sms 的缓存
This commit is contained in:
		@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SysSmsChannelDO> {
 | 
			
		||||
                .orderByDesc("id"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default List<SysSmsChannelDO> selectListByStatus(Integer status) {
 | 
			
		||||
        return selectList(new LambdaQueryWrapper<SysSmsChannelDO>().eq(SysSmsChannelDO::getStatus, status)
 | 
			
		||||
                .orderByAsc(SysSmsChannelDO::getId));
 | 
			
		||||
    }
 | 
			
		||||
    @Select("SELECT id FROM sys_sms_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
 | 
			
		||||
    Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SysSmsTemplateDO> {
 | 
			
		||||
        return selectCount("channel_id", channelId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Select("SELECT id FROM sys_sms_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
 | 
			
		||||
    Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SysSmsChannelRefreshMessage> {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SysSmsChannelService smsChannelService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMessage(SysSmsChannelRefreshMessage message) {
 | 
			
		||||
        log.info("[onMessage][收到 SmsChannel 刷新消息]");
 | 
			
		||||
        smsChannelService.initSmsClients();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<SysSmsTemplateRefreshMessage> {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SysSmsTemplateService smsTemplateService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMessage(SysSmsTemplateRefreshMessage message) {
 | 
			
		||||
        log.info("[onMessage][收到 SmsTemplate 刷新消息]");
 | 
			
		||||
        smsTemplateService.initLocalCache();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -9,6 +9,8 @@ import javax.annotation.Resource;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Role 角色相关消息的 Producer
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class SysRoleProducer {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package cn.iocoder.dashboard.modules.system.service;
 | 
			
		||||
@@ -18,7 +18,7 @@ import java.util.Set;
 | 
			
		||||
public interface SysPermissionService extends SecurityPermissionFrameworkService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化
 | 
			
		||||
     * 初始化权限的本地缓存
 | 
			
		||||
     */
 | 
			
		||||
    void initLocalCache();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService {
 | 
			
		||||
     */
 | 
			
		||||
    private volatile Map<Long, SysRoleDO> roleCache;
 | 
			
		||||
    /**
 | 
			
		||||
     * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
 | 
			
		||||
     * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
 | 
			
		||||
     */
 | 
			
		||||
    private volatile Date maxUpdateTime;
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService {
 | 
			
		||||
    @Override
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void initLocalCache() {
 | 
			
		||||
        // 获取菜单列表,如果有更新
 | 
			
		||||
        // 获取角色列表,如果有更新
 | 
			
		||||
        List<SysRoleDO> 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<SysRoleDO> 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 格式化短信内容
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SysSmsChannelDO> channelDOList = smsChannelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
 | 
			
		||||
        // 创建渠道 Client
 | 
			
		||||
        List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(channelDOList);
 | 
			
		||||
        // 获取短信渠道,如果有更新
 | 
			
		||||
        List<SysSmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
 | 
			
		||||
        if (CollUtil.isEmpty(smsChannels)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 创建或更新短信 Client
 | 
			
		||||
        List<SmsChannelProperties> 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<SysSmsChannelDO> 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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<String, SysSmsTemplateDO> 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<SysSmsTemplateDO> smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
 | 
			
		||||
        if (CollUtil.isEmpty(smsTemplateList)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 写入缓存
 | 
			
		||||
        ImmutableMap.Builder<String, SysSmsTemplateDO> 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<SysSmsTemplateDO> 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<String, Object> 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) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user