增加 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>
</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>

View File

@ -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>

View File

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

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

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.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);
}

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
*
* @author
*/
@Component
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.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);
}
}

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 {
/**
*
*
*/
void initLocalCache();

View File

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

View File

@ -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);
/**
*
*

View File

@ -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) {

View File

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

View File

@ -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) {