增加 sms 的缓存
parent
be8b892bf6
commit
953d270dd9
|
@ -84,7 +84,7 @@
|
|||
<div>{{ parseTime(scope.row.receiveTime) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="短信渠道" align="center" width="100">
|
||||
<el-table-column label="短信渠道" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
|
||||
<div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" />
|
||||
<el-table-column label="短信渠道" align="center">
|
||||
<el-table-column label="短信渠道" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
|
||||
<div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
|
||||
|
|
|
@ -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);
|
||||
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
|
||||
// 获取短信渠道,如果有更新
|
||||
List<SysSmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(smsChannels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO 芋艿:刷新缓存
|
||||
// 创建或更新短信 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());
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
|
Loading…
Reference in New Issue