增加 sms 的缓存

pull/2/head
YunaiV 2021-04-14 00:34:16 +08:00
parent be8b892bf6
commit 953d270dd9
18 changed files with 306 additions and 32 deletions

View File

@ -84,7 +84,7 @@
<div>{{ parseTime(scope.row.receiveTime) }}</div> <div>{{ parseTime(scope.row.receiveTime) }}</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="短信渠道" align="center" width="100"> <el-table-column label="短信渠道" align="center" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div> <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div> <div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div>

View File

@ -68,7 +68,7 @@
</el-table-column> </el-table-column>
<el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" /> <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"> <template slot-scope="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div> <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div> <div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div>

View File

@ -50,7 +50,7 @@ public abstract class AbstractSmsClient implements SmsClient {
public final void refresh(SmsChannelProperties properties) { public final void refresh(SmsChannelProperties properties) {
// 判断是否更新 // 判断是否更新
if (!properties.equals(this.properties)) { if (properties.equals(this.properties)) {
return; return;
} }
log.info("[refresh][配置({})发生变化,重新初始化]", properties); log.info("[refresh][配置({})发生变化,重新初始化]", properties);

View File

@ -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 cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
@ -21,9 +23,7 @@ public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> {
.orderByDesc("id")); .orderByDesc("id"));
} }
default List<SysSmsChannelDO> selectListByStatus(Integer status) { @Select("SELECT id FROM sys_sms_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
return selectList(new LambdaQueryWrapper<SysSmsChannelDO>().eq(SysSmsChannelDO::getStatus, status) Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
.orderByAsc(SysSmsChannelDO::getId));
}
} }

View File

@ -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.controller.sms.vo.template.SysSmsTemplatePageReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
@ -45,4 +47,7 @@ public interface SysSmsTemplateMapper extends BaseMapperX<SysSmsTemplateDO> {
return selectCount("channel_id", channelId); return selectCount("channel_id", channelId);
} }
@Select("SELECT id FROM sys_sms_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
} }

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -9,6 +9,8 @@ import javax.annotation.Resource;
/** /**
* Role Producer * Role Producer
*
* @author
*/ */
@Component @Component
public class SysRoleProducer { public class SysRoleProducer {

View File

@ -2,7 +2,9 @@ package cn.iocoder.dashboard.modules.system.mq.producer.sms;
import cn.iocoder.dashboard.common.core.KeyValue; import cn.iocoder.dashboard.common.core.KeyValue;
import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; 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.SysSmsSendMessage;
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -11,7 +13,7 @@ import javax.annotation.Resource;
import java.util.List; import java.util.List;
/** /**
* * Sms Producer
* *
* @author zzf * @author zzf
* @date 2021/3/9 16:35 * @date 2021/3/9 16:35
@ -24,7 +26,7 @@ public class SysSmsProducer {
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
/** /**
* Message * {@link SysSmsSendMessage}
* *
* @param logId * @param logId
* @param mobile * @param mobile
@ -39,4 +41,20 @@ public class SysSmsProducer {
RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message); 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);
}
} }

View File

@ -1 +0,0 @@
package cn.iocoder.dashboard.modules.system.service;

View File

@ -18,7 +18,7 @@ import java.util.Set;
public interface SysPermissionService extends SecurityPermissionFrameworkService { public interface SysPermissionService extends SecurityPermissionFrameworkService {
/** /**
* *
*/ */
void initLocalCache(); void initLocalCache();

View File

@ -58,7 +58,7 @@ public class SysRoleServiceImpl implements SysRoleService {
*/ */
private volatile Map<Long, SysRoleDO> roleCache; private volatile Map<Long, SysRoleDO> roleCache;
/** /**
* *
*/ */
private volatile Date maxUpdateTime; private volatile Date maxUpdateTime;
@ -77,7 +77,7 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override @Override
@PostConstruct @PostConstruct
public void initLocalCache() { public void initLocalCache() {
// 获取菜单列表,如果有更新 // 获取角色列表,如果有更新
List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime); List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(roleList)) { if (CollUtil.isEmpty(roleList)) {
return; return;
@ -98,23 +98,23 @@ public class SysRoleServiceImpl implements SysRoleService {
} }
/** /**
* *
* *
* *
* @param maxUpdateTime * @param maxUpdateTime
* @return * @return
*/ */
private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) { private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) {
// 第一步,判断是否要更新。 // 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadRoleIfUpdate][首次加载全量菜单]"); log.info("[loadRoleIfUpdate][首次加载全量角色]");
} else { // 判断数据库中是否有更新的菜单 } else { // 判断数据库中是否有更新的角色
if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
return null; return null;
} }
log.info("[loadRoleIfUpdate][增量加载全量菜单]"); log.info("[loadRoleIfUpdate][增量加载全量角色]");
} }
// 第二步,如果有更新,则从数据库加载所有菜单 // 第二步,如果有更新,则从数据库加载所有角色
return roleMapper.selectList(); return roleMapper.selectList();
} }

View File

@ -20,6 +20,11 @@ import java.util.Map;
*/ */
public interface SysSmsTemplateService { public interface SysSmsTemplateService {
/**
*
*/
void initLocalCache();
/** /**
* *
* *
@ -28,6 +33,14 @@ public interface SysSmsTemplateService {
*/ */
SysSmsTemplateDO getSmsTemplateByCode(String code); SysSmsTemplateDO getSmsTemplateByCode(String code);
/**
*
*
* @param code
* @return
*/
SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code);
/** /**
* *
* *

View File

@ -1,7 +1,8 @@
package cn.iocoder.dashboard.modules.system.service.sms.impl; 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.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.client.SmsClientFactory;
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO; 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.convert.sms.SysSmsChannelConvert;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO; 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.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.SysSmsChannelService;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; 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 org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.List; import java.util.List;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; 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 * @date 2021/1/25 9:25
*/ */
@Service @Service
@Slf4j
public class SysSmsChannelServiceImpl implements SysSmsChannelService { public class SysSmsChannelServiceImpl implements SysSmsChannelService {
/**
* {@link #schedulePeriodicRefresh()}
* Redis Pub/Sub
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
*
*/
private volatile Date maxUpdateTime;
@Resource @Resource
private SmsClientFactory smsClientFactory; private SmsClientFactory smsClientFactory;
@ -41,23 +59,61 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
@Resource @Resource
private SysSmsTemplateService smsTemplateService; private SysSmsTemplateService smsTemplateService;
@Resource
private SysSmsProducer smsProducer;
@Override @Override
@PostConstruct @PostConstruct
public void initSmsClients() { public void initSmsClients() {
// 查询有效渠道信息 // 获取短信渠道,如果有更新
List<SysSmsChannelDO> channelDOList = smsChannelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); List<SysSmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
// 创建渠道 Client if (CollUtil.isEmpty(smsChannels)) {
List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(channelDOList); return;
}
// 创建或更新短信 Client
List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(smsChannels);
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties)); 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 @Override
public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) { public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) {
// 插入 // 插入
SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO); SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO);
smsChannelMapper.insert(smsChannel); smsChannelMapper.insert(smsChannel);
// 发送刷新消息
smsProducer.sendSmsChannelRefreshMessage();
// 返回 // 返回
return smsChannel.getId(); return smsChannel.getId();
} }
@ -69,6 +125,8 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
// 更新 // 更新
SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO); SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO);
smsChannelMapper.updateById(updateObj); smsChannelMapper.updateById(updateObj);
// 发送刷新消息
smsProducer.sendSmsChannelRefreshMessage();
} }
@Override @Override
@ -79,8 +137,10 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
if (smsTemplateService.countByChannelId(id) > 0) { if (smsTemplateService.countByChannelId(id) > 0) {
throw exception(SMS_CHANNEL_HAS_CHILDREN); throw exception(SMS_CHANNEL_HAS_CHILDREN);
} }
// 更新 // 删除
smsChannelMapper.deleteById(id); smsChannelMapper.deleteById(id);
// 发送刷新消息
smsProducer.sendSmsChannelRefreshMessage();
} }
private void validateSmsChannelExists(Long id) { private void validateSmsChannelExists(Long id) {

View File

@ -98,8 +98,9 @@ public class SysSmsServiceImpl implements SysSmsService {
} }
private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) { private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
// 获得短信模板。考虑到效率,从缓存中获取
SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
// 短信模板不存在 // 短信模板不存在
SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCode(templateCode);
if (template == null) { if (template == null) {
throw exception(SMS_TEMPLATE_NOT_EXISTS); throw exception(SMS_TEMPLATE_NOT_EXISTS);
} }

View File

@ -1,9 +1,11 @@
package cn.iocoder.dashboard.modules.system.service.sms.impl; 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.ReUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult; 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.SmsClient;
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult; 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.SysSmsChannelDO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO; 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.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.SysSmsChannelService;
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService; import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
import com.google.common.annotations.VisibleForTesting; 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.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; 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 * @date 2021/1/25 9:25
*/ */
@Service @Service
@Slf4j
public class SysSmsTemplateServiceImpl implements SysSmsTemplateService { public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
/** /**
@ -46,6 +51,24 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
*/ */
private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}"); 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 @Resource
private SysSmsTemplateMapper smsTemplateMapper; private SysSmsTemplateMapper smsTemplateMapper;
@ -55,11 +78,66 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
@Resource @Resource
private SmsClientFactory smsClientFactory; 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 @Override
public SysSmsTemplateDO getSmsTemplateByCode(String code) { public SysSmsTemplateDO getSmsTemplateByCode(String code) {
return smsTemplateMapper.selectByCode(code); return smsTemplateMapper.selectByCode(code);
} }
@Override
public SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
return smsTemplateCache.get(code);
}
@Override @Override
public String formatSmsTemplateContent(String content, Map<String, Object> params) { public String formatSmsTemplateContent(String content, Map<String, Object> params) {
return StrUtil.format(content, params); return StrUtil.format(content, params);
@ -84,6 +162,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
template.setParams(parseTemplateContentParams(template.getContent())); template.setParams(parseTemplateContentParams(template.getContent()));
template.setChannelCode(channelDO.getCode()); template.setChannelCode(channelDO.getCode());
smsTemplateMapper.insert(template); smsTemplateMapper.insert(template);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
// 返回 // 返回
return template.getId(); return template.getId();
} }
@ -104,6 +184,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
updateObj.setParams(parseTemplateContentParams(updateObj.getContent())); updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
updateObj.setChannelCode(channelDO.getCode()); updateObj.setChannelCode(channelDO.getCode());
smsTemplateMapper.updateById(updateObj); smsTemplateMapper.updateById(updateObj);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
} }
@Override @Override
@ -112,6 +194,8 @@ public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
this.validateSmsTemplateExists(id); this.validateSmsTemplateExists(id);
// 更新 // 更新
smsTemplateMapper.deleteById(id); smsTemplateMapper.deleteById(id);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
} }
private void validateSmsTemplateExists(Long id) { private void validateSmsTemplateExists(Long id) {