mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 04:08:43 +08:00 
			
		
		
		
	sms 缓存,使用 guava 替代 job 扫描,目的:提升启动速度,加快缓存失效
This commit is contained in:
		@@ -32,8 +32,8 @@ public interface SmsChannelConvert {
 | 
			
		||||
 | 
			
		||||
    PageResult<SmsChannelRespVO> convertPage(PageResult<SmsChannelDO> page);
 | 
			
		||||
 | 
			
		||||
    List<SmsChannelProperties> convertList02(List<SmsChannelDO> list);
 | 
			
		||||
 | 
			
		||||
    List<SmsChannelSimpleRespVO> convertList03(List<SmsChannelDO> list);
 | 
			
		||||
 | 
			
		||||
    SmsChannelProperties convert02(SmsChannelDO channel);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Select;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
 | 
			
		||||
@@ -21,7 +18,8 @@ public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
 | 
			
		||||
                .orderByDesc(SmsChannelDO::getId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
 | 
			
		||||
    Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
 | 
			
		||||
    default SmsChannelDO selectByCode(String code) {
 | 
			
		||||
        return selectOne(SmsChannelDO::getCode, code);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.service.sms;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
 | 
			
		||||
@@ -13,15 +14,10 @@ import java.util.List;
 | 
			
		||||
 * 短信渠道 Service 接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author zzf
 | 
			
		||||
 * @date 2021/1/25 9:24
 | 
			
		||||
 * @since 2021/1/25 9:24
 | 
			
		||||
 */
 | 
			
		||||
public interface SmsChannelService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化短信客户端
 | 
			
		||||
     */
 | 
			
		||||
    void initLocalCache();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建短信渠道
 | 
			
		||||
     *
 | 
			
		||||
@@ -67,4 +63,20 @@ public interface SmsChannelService {
 | 
			
		||||
     */
 | 
			
		||||
    PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得短信客户端
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 编号
 | 
			
		||||
     * @return 短信客户端
 | 
			
		||||
     */
 | 
			
		||||
    SmsClient getSmsClient(Long id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得短信客户端
 | 
			
		||||
     *
 | 
			
		||||
     * @param code 编码
 | 
			
		||||
     * @return 短信客户端
 | 
			
		||||
     */
 | 
			
		||||
    SmsClient getSmsClient(String code);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.service.sms;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
 | 
			
		||||
@@ -10,20 +12,18 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannel
 | 
			
		||||
import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
 | 
			
		||||
import com.google.common.cache.CacheLoader;
 | 
			
		||||
import com.google.common.cache.LoadingCache;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
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.time.LocalDateTime;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
 | 
			
		||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
 | 
			
		||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
 | 
			
		||||
 | 
			
		||||
@@ -37,10 +37,44 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
 | 
			
		||||
public class SmsChannelServiceImpl implements SmsChannelService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信渠道列表的缓存
 | 
			
		||||
     * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
 | 
			
		||||
     */
 | 
			
		||||
    @Getter
 | 
			
		||||
    private volatile List<SmsChannelDO> channelCache = Collections.emptyList();
 | 
			
		||||
    private final LoadingCache<Long, SmsClient> idClientCache = CacheUtils.buildAsyncReloadingCache(Duration.ofSeconds(10L),
 | 
			
		||||
            new CacheLoader<Long, SmsClient>() {
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public SmsClient load(Long id) {
 | 
			
		||||
                    // 查询,然后尝试刷新
 | 
			
		||||
                    SmsChannelDO channel = smsChannelMapper.selectById(id);
 | 
			
		||||
                    if (channel != null) {
 | 
			
		||||
                        SmsChannelProperties properties = SmsChannelConvert.INSTANCE.convert02(channel);
 | 
			
		||||
                        smsClientFactory.createOrUpdateSmsClient(properties);
 | 
			
		||||
                    }
 | 
			
		||||
                    return smsClientFactory.getSmsClient(id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
 | 
			
		||||
     */
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final LoadingCache<String, SmsClient> codeClientCache = CacheUtils.buildAsyncReloadingCache(Duration.ofSeconds(60L),
 | 
			
		||||
            new CacheLoader<String, SmsClient>() {
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public SmsClient load(String code) {
 | 
			
		||||
                    // 查询,然后尝试刷新
 | 
			
		||||
                    SmsChannelDO channel = smsChannelMapper.selectByCode(code);
 | 
			
		||||
                    if (channel != null) {
 | 
			
		||||
                        SmsChannelProperties properties = SmsChannelConvert.INSTANCE.convert02(channel);
 | 
			
		||||
                        smsClientFactory.createOrUpdateSmsClient(properties);
 | 
			
		||||
                    }
 | 
			
		||||
                    return smsClientFactory.getSmsClient(code);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmsClientFactory smsClientFactory;
 | 
			
		||||
@@ -51,66 +85,33 @@ public class SmsChannelServiceImpl implements SmsChannelService {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmsTemplateService smsTemplateService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void initLocalCache() {
 | 
			
		||||
        // 第一步:查询数据
 | 
			
		||||
        List<SmsChannelDO> channels = smsChannelMapper.selectList();
 | 
			
		||||
        log.info("[initLocalCache][缓存短信渠道,数量为:{}]", channels.size());
 | 
			
		||||
 | 
			
		||||
        // 第二步:构建缓存:创建或更新短信 Client
 | 
			
		||||
        List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
 | 
			
		||||
        propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
 | 
			
		||||
        this.channelCache = channels;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过定时任务轮询,刷新缓存
 | 
			
		||||
     *
 | 
			
		||||
     * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
 | 
			
		||||
     */
 | 
			
		||||
    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
 | 
			
		||||
    public void refreshLocalCache() {
 | 
			
		||||
        // 情况一:如果缓存里没有数据,则直接刷新缓存
 | 
			
		||||
        if (CollUtil.isEmpty(channelCache)) {
 | 
			
		||||
            initLocalCache();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
 | 
			
		||||
        LocalDateTime maxTime = getMaxValue(channelCache, SmsChannelDO::getUpdateTime);
 | 
			
		||||
        if (smsChannelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
 | 
			
		||||
            initLocalCache();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Long createSmsChannel(SmsChannelCreateReqVO createReqVO) {
 | 
			
		||||
        // 插入
 | 
			
		||||
        SmsChannelDO smsChannel = SmsChannelConvert.INSTANCE.convert(createReqVO);
 | 
			
		||||
        smsChannelMapper.insert(smsChannel);
 | 
			
		||||
        SmsChannelDO channel = SmsChannelConvert.INSTANCE.convert(createReqVO);
 | 
			
		||||
        smsChannelMapper.insert(channel);
 | 
			
		||||
 | 
			
		||||
        // 刷新缓存
 | 
			
		||||
        initLocalCache();
 | 
			
		||||
        return smsChannel.getId();
 | 
			
		||||
        // 清空缓存
 | 
			
		||||
        clearCache(channel.getId(), null);
 | 
			
		||||
        return channel.getId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateSmsChannel(SmsChannelUpdateReqVO updateReqVO) {
 | 
			
		||||
        // 校验存在
 | 
			
		||||
        validateSmsChannelExists(updateReqVO.getId());
 | 
			
		||||
        SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId());
 | 
			
		||||
        // 更新
 | 
			
		||||
        SmsChannelDO updateObj = SmsChannelConvert.INSTANCE.convert(updateReqVO);
 | 
			
		||||
        smsChannelMapper.updateById(updateObj);
 | 
			
		||||
 | 
			
		||||
        // 刷新缓存
 | 
			
		||||
        initLocalCache();
 | 
			
		||||
        // 清空缓存
 | 
			
		||||
        clearCache(updateReqVO.getId(), channel.getCode());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteSmsChannel(Long id) {
 | 
			
		||||
        // 校验存在
 | 
			
		||||
        validateSmsChannelExists(id);
 | 
			
		||||
        SmsChannelDO channel = validateSmsChannelExists(id);
 | 
			
		||||
        // 校验是否有在使用该账号的模版
 | 
			
		||||
        if (smsTemplateService.countByChannelId(id) > 0) {
 | 
			
		||||
            throw exception(SMS_CHANNEL_HAS_CHILDREN);
 | 
			
		||||
@@ -118,14 +119,28 @@ public class SmsChannelServiceImpl implements SmsChannelService {
 | 
			
		||||
        // 删除
 | 
			
		||||
        smsChannelMapper.deleteById(id);
 | 
			
		||||
 | 
			
		||||
        // 刷新缓存
 | 
			
		||||
        initLocalCache();
 | 
			
		||||
        // 清空缓存
 | 
			
		||||
        clearCache(id, channel.getCode());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void validateSmsChannelExists(Long id) {
 | 
			
		||||
        if (smsChannelMapper.selectById(id) == null) {
 | 
			
		||||
    /**
 | 
			
		||||
     * 清空指定渠道编号的缓存
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 渠道编号
 | 
			
		||||
     */
 | 
			
		||||
    private void clearCache(Long id, String code) {
 | 
			
		||||
        idClientCache.invalidate(id);
 | 
			
		||||
        if (StrUtil.isNotEmpty(code)) {
 | 
			
		||||
            codeClientCache.invalidate(code);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SmsChannelDO validateSmsChannelExists(Long id) {
 | 
			
		||||
        SmsChannelDO channel = smsChannelMapper.selectById(id);
 | 
			
		||||
        if (channel == null) {
 | 
			
		||||
            throw exception(SMS_CHANNEL_NOT_EXISTS);
 | 
			
		||||
        }
 | 
			
		||||
        return channel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -143,4 +158,14 @@ public class SmsChannelServiceImpl implements SmsChannelService {
 | 
			
		||||
        return smsChannelMapper.selectPage(pageReqVO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SmsClient getSmsClient(Long id) {
 | 
			
		||||
        return idClientCache.getUnchecked(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SmsClient getSmsClient(String code) {
 | 
			
		||||
        return codeClientCache.getUnchecked(code);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
 | 
			
		||||
@@ -49,9 +48,6 @@ public class SmsSendServiceImpl implements SmsSendService {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmsLogService smsLogService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmsClientFactory smsClientFactory;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private SmsProducer smsProducer;
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +91,6 @@ public class SmsSendServiceImpl implements SmsSendService {
 | 
			
		||||
        // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
 | 
			
		||||
        Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())
 | 
			
		||||
                && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());
 | 
			
		||||
        ;
 | 
			
		||||
        String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
 | 
			
		||||
        Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
 | 
			
		||||
 | 
			
		||||
@@ -132,7 +127,7 @@ public class SmsSendServiceImpl implements SmsSendService {
 | 
			
		||||
    /**
 | 
			
		||||
     * 将参数模板,处理成有序的 KeyValue 数组
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说腾讯云 https://cloud.tencent.com/document/product/382/39023
 | 
			
		||||
     * 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说 <a href="https://cloud.tencent.com/document/product/382/39023">腾讯云</a>
 | 
			
		||||
     *
 | 
			
		||||
     * @param template       短信模板
 | 
			
		||||
     * @param templateParams 原始参数
 | 
			
		||||
@@ -160,7 +155,7 @@ public class SmsSendServiceImpl implements SmsSendService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doSendSms(SmsSendMessage message) {
 | 
			
		||||
        // 获得渠道对应的 SmsClient 客户端
 | 
			
		||||
        SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
 | 
			
		||||
        SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());
 | 
			
		||||
        Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
 | 
			
		||||
        // 发送短信
 | 
			
		||||
        SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
 | 
			
		||||
@@ -173,7 +168,7 @@ public class SmsSendServiceImpl implements SmsSendService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receiveSmsStatus(String channelCode, String text) throws Throwable {
 | 
			
		||||
        // 获得渠道对应的 SmsClient 客户端
 | 
			
		||||
        SmsClient smsClient = smsClientFactory.getSmsClient(channelCode);
 | 
			
		||||
        SmsClient smsClient = smsChannelService.getSmsClient(channelCode);
 | 
			
		||||
        Assert.notNull(smsClient, "短信客户端({}) 不存在", channelCode);
 | 
			
		||||
        // 解析内容
 | 
			
		||||
        List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user