重构短信功能

This commit is contained in:
zengzefeng 2021-01-28 16:45:25 +08:00
parent df8bda53e8
commit 37c39365ec
42 changed files with 455 additions and 567 deletions

View File

@ -892,9 +892,8 @@ DROP TABLE IF EXISTS `sms_channel`;
CREATE TABLE `sms_channel` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
`code` varchar(50) not null COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
`api_key` varchar(100) NOT NULL COMMENT '账号id', -- add
`api_secret` varchar(100) NOT NULL COMMENT '账号秘钥', -- add
`priority` tinyint(8) NOT NULL default 1 COMMENT '优先级(存在多个签名时,选择值最小的,渠道不可用时,按优先级从小到大切换)', -- add
`api_key` varchar(100) NOT NULL COMMENT '账号id',
`api_secret` varchar(100) NOT NULL COMMENT '账号秘钥',
`api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
`name` varchar(50) not null COMMENT '名称',
`signature` varchar(50) not null COMMENT '签名值',
@ -919,15 +918,14 @@ DROP TABLE IF EXISTS `sms_template`;
CREATE TABLE `sms_template` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
`channel_code` varchar(50) not null COMMENT '短信渠道编码(来自枚举类)',
`channel_id` bigint(20) not null COMMENT '短信渠道id (对于前端来说就是绑定一个签名)', -- add
`channel_id` bigint(20) not null COMMENT '短信渠道id (对于前端来说就是绑定一个签名)',
`type` tinyint(4) NOT NULL default 1 COMMENT '消息类型 [0验证码 1短信通知 2推广短信 3国际/港澳台消息]',
`biz_code` varchar(50) not null COMMENT '业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)', -- add
`priority` tinyint(8) NOT NULL default 1 COMMENT '优先级(默认直接继承渠道表的,逻辑也与渠道表的一致,可以针对每个biz_code进行修改)',-- add
`biz_code` varchar(50) not null COMMENT '业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)',
`code` varchar(50) not null COMMENT '编码',
`name` varchar(50) not null COMMENT '名称',
`api_template_id` varchar(100) NOT NULL COMMENT '实际渠道模板唯一标识',
`content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
`params` varchar(200) NOT NULL DEFAULT '' COMMENT '参数数组(自动根据内容生成)', -- add
`params` varchar(200) NOT NULL DEFAULT '' COMMENT '参数数组(自动根据内容生成)',
`remark` varchar(200) NOT NULL COMMENT '备注',
`status` tinyint(4) NOT NULL default 0 COMMENT '启用状态0正常 1停用',

View File

@ -1,67 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* 短信父接口
*
* @author zzf
* @date 2021/1/25 14:14
*/
public interface SmsSender<R> {
/**
* 发送通知
*
* @param msgBody 通知内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
SmsResult<R> send(SmsBody msgBody, Collection<String> targets);
/**
* 发送通知
*
* @param msgBody 通知内容
* @param target 发送对象列表
* @return 是否发送成功
*/
default SmsResult<R> send(SmsBody msgBody, String target) {
if (StringUtils.isBlank(target)) {
return failResult();
}
return send(msgBody, Collections.singletonList(target));
}
/**
* 发送通知
*
* @param msgBody 通知内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
default SmsResult<R> send(SmsBody msgBody, String... targets) {
if (targets == null) {
return failResult();
}
return send(msgBody, Arrays.asList(targets));
}
default SmsResult<R> failResult() {
SmsResult<R> resultBody = new SmsResult<>();
resultBody.setSuccess(false);
return resultBody;
}
default SmsResult<R> failResult(String message) {
SmsResult<R> resultBody = failResult();
resultBody.setMessage(message);
return resultBody;
}
}

View File

@ -1,48 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.config;
import cn.iocoder.dashboard.framework.msg.sms.factory.DefaultSmsSenderFactory;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.DefaultSmsIntercepterChain;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.SmsLogIntercepter;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.List;
/**
* 短信服务配置
*
* @author guer
*/
@Configuration
@ConditionalOnProperty("sms.enabled")
public class SmsConfiguration {
@Resource
private SmsChannelService channelService;
@Bean
public AbstractSmsIntercepterChain smsIntercepterChain() {
DefaultSmsIntercepterChain intercepterChain = new DefaultSmsIntercepterChain();
//添加拦截器
intercepterChain.addSmsIntercepter(new SmsLogIntercepter());
return intercepterChain;
}
@Bean
public DefaultSmsSenderFactory smsSenderFactory(AbstractSmsIntercepterChain intercepterChain) {
DefaultSmsSenderFactory defaultSmsSenderFactory = new DefaultSmsSenderFactory();
List<SmsChannelAllVO> smsChannelAllVOList = channelService.listChannelAllEnabledInfo();
//初始化渠道模板信息
defaultSmsSenderFactory.init(smsChannelAllVOList);
//注入拦截器链
defaultSmsSenderFactory.setIntercepterChain(intercepterChain);
return defaultSmsSenderFactory;
}
}

View File

@ -1,137 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.factory;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
import cn.iocoder.dashboard.framework.msg.sms.impl.ali.AliSmsSender;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
import cn.iocoder.dashboard.framework.msg.sms.proxy.DefaultSmsSenderProxy;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsTemplateVO;
import lombok.Setter;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
/**
* 短信发送者工厂
*
* @author zzf
* @date 2021/1/25 16:18
*/
public class DefaultSmsSenderFactory {
/**
* sender索引
* key: {@link SmsTemplateVO#getBizCode()}
* value: {@link SmsSender}
*/
private final ConcurrentHashMap<String, SmsSender<?>> bizCode2SenderMap = new ConcurrentHashMap<>(8);
/**
* sender索引
* key: {@link SmsTemplateVO#getCode()}
* value: {@link SmsSender}
*/
private final ConcurrentHashMap<String, SmsSender<?>> templateCode2SenderMap = new ConcurrentHashMap<>(8);
@Setter
private AbstractSmsIntercepterChain intercepterChain;
/**
* 读写锁
*/
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public void init(List<SmsChannelAllVO> smsChannelAllVOList) {
if (ObjectUtil.isEmpty(smsChannelAllVOList)) {
throw new ServiceException(SMS_CHANNEL_NOT_FOUND);
}
try {
writeLock.lock();
addSender(smsChannelAllVOList);
} finally {
writeLock.unlock();
}
}
public SmsSender<?> getSenderByBizCode(String bizCode) {
return getSmsSender(bizCode, bizCode2SenderMap);
}
public SmsSender<?> getSenderByTemplateCode(String templateCode) {
return getSmsSender(templateCode, templateCode2SenderMap);
}
private SmsSender<?> getSmsSender(String templateCode, ConcurrentHashMap<String, SmsSender<?>> cacheMap) {
try {
readLock.lock();
SmsSender<?> smsSender = cacheMap.get(templateCode);
if (smsSender == null) {
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
return smsSender;
} finally {
readLock.unlock();
}
}
public void flush(List<SmsChannelAllVO> smsChannelAllVOList) {
try {
writeLock.lock();
bizCode2SenderMap.clear();
templateCode2SenderMap.clear();
addSender(smsChannelAllVOList);
} finally {
writeLock.unlock();
}
}
private void addSender(List<SmsChannelAllVO> smsChannelAllVOList) {
smsChannelAllVOList.forEach(channelAllVO -> addSender(SmsChannelEnum.getByCode(channelAllVO.getCode()), channelAllVO));
}
private void addSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
if (channelEnum == null) {
throw new ServiceException(INVALID_CHANNEL_CODE);
}
List<SmsTemplateVO> templateList = channelAllVO.getTemplateList();
if (ObjectUtil.isEmpty(templateList)) {
throw new ServiceException(SMS_TEMPLATE_NOT_FOUND);
}
SmsSender<?> aliSmsSender = getSender(channelEnum, channelAllVO);
templateList.forEach(smsTemplateVO -> {
bizCode2SenderMap.put(smsTemplateVO.getBizCode(), aliSmsSender);
templateCode2SenderMap.put(smsTemplateVO.getCode(), aliSmsSender);
});
}
private SmsSender<?> getSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
switch (channelEnum) {
case ALI:
return new DefaultSmsSenderProxy<>(new AliSmsSender(channelAllVO), intercepterChain);
// TODO fill more channel
default:
break;
}
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.intercepter;
import lombok.Getter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
public abstract class AbstractSmsIntercepterChain {
@Getter
protected final List<SmsIntercepter> intercepterList = new ArrayList<>(8);
/**
* 添加短信拦截器
*
* @param smsIntercepter 短信拦截器
*/
public void addSmsIntercepter(SmsIntercepter smsIntercepter) {
addSmsIntercepter(Collections.singletonList(smsIntercepter));
}
/**
* 添加短信拦截器
*
* @param smsIntercepterList 短信拦截器数组
*/
abstract void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList);
}

View File

@ -1,20 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.intercepter;
import java.util.Comparator;
import java.util.List;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
public class DefaultSmsIntercepterChain extends AbstractSmsIntercepterChain {
@Override
public void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList) {
intercepterList.addAll(smsIntercepterList);
//排序
intercepterList.sort(Comparator.comparingInt(SmsIntercepter::getOrder));
}
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.intercepter;
import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
import java.util.Collection;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
public interface SmsIntercepter {
/**
* 监听发送前
*
* @param msgBody 消息体
* @param targets 发送对象数组
*/
void beforeSender(SmsBody msgBody, Collection<String> targets);
/**
* 监听发送后
*
* @param msgBody 消息体
* @param targets 发送对象数组
* @param resultBody 返回对象
*/
<T> void afterSender(SmsBody msgBody, Collection<String> targets, SmsResult<T> resultBody);
/**
* 排序值拦截器根据order值顺序执行
* <p>
* 值越小越早执行
*
* @return 排序值
*/
int getOrder();
}

View File

@ -1,46 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.intercepter;
import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsLogMapper;
import cn.iocoder.dashboard.util.json.JsonUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.util.Collection;
/**
* 短信日志拦截器
*
* @author zzf
* @date 2021/1/22 15:46
*/
@Slf4j
public class SmsLogIntercepter implements SmsIntercepter {
@Override
public void beforeSender(SmsBody msgBody, Collection<String> targets) {
log.debug("ready send sms, body: {}, target: {}", JsonUtils.toJsonString(msgBody), targets);
}
@Override
public <T> void afterSender(SmsBody msgBody, Collection<String> targets, SmsResult<T> resultBody) {
if (resultBody.getSuccess()) {
//
} else {
log.warn("send sms fail, body: {}, target: {}, resultBody: {}",
JsonUtils.toJsonString(msgBody),
targets,
JsonUtils.toJsonString(resultBody)
);
}
}
@Override
public int getOrder() {
return 0;
}
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.proxy;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.AbstractSmsIntercepterChain;
import java.util.Collection;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
public class DefaultSmsSenderProxy<R> implements SmsSender<R> {
private final SmsSender<R> smsSender;
private final AbstractSmsIntercepterChain chain;
@Override
public SmsResult<R> send(SmsBody msgBody, Collection<String> targets) {
if (ObjectUtil.isNotNull(chain) && ObjectUtil.isNotEmpty(chain.getIntercepterList())) {
chain.getIntercepterList().forEach(s -> s.beforeSender(msgBody, targets));
}
SmsResult<R> resultBody = smsSender.send(msgBody, targets);
if (ObjectUtil.isNotNull(chain) && ObjectUtil.isNotEmpty(chain.getIntercepterList())) {
chain.getIntercepterList().forEach(s -> s.afterSender(msgBody, targets, resultBody));
}
return resultBody;
}
public DefaultSmsSenderProxy(SmsSender<R> smsSender,
AbstractSmsIntercepterChain chain) {
this.smsSender = smsSender;
this.chain = chain;
}
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.dashboard.framework.msg.sms.proxy;
import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
import cn.iocoder.dashboard.framework.msg.sms.intercepter.SmsIntercepter;
import java.util.Collections;
import java.util.List;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
public interface SmsSenderProxy<R> extends SmsSender<R> {
/**
* 添加短信拦截器
*
* @param smsIntercepter 短信拦截器
*/
default void addSmsIntercepter(SmsIntercepter smsIntercepter) {
addSmsIntercepter(Collections.singletonList(smsIntercepter));
}
/**
* 添加短信拦截器
*
* @param smsIntercepterList 短信拦截器数组
*/
void addSmsIntercepter(List<SmsIntercepter> smsIntercepterList);
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.framework.msg.sms;
package cn.iocoder.dashboard.framework.sms;
import cn.iocoder.dashboard.util.json.JsonUtils;
import lombok.Data;

View File

@ -0,0 +1,107 @@
package cn.iocoder.dashboard.framework.sms;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* 短信父接口
*
* @author zzf
* @date 2021/1/25 14:14
*/
public interface SmsClient<R> {
/**
* 发送消息
*
* @param msgBody 消息内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
SmsResult<R> send(SmsBody msgBody, Collection<String> targets);
/**
* 发送消息
*
* @param msgBody 消息内容
* @param target 发送对象
* @return 是否发送成功
*/
default SmsResult<R> send(SmsBody msgBody, String target) {
if (StringUtils.isBlank(target)) {
return failResult();
}
return send(msgBody, Collections.singletonList(target));
}
/**
* 发送消息
*
* @param msgBody 消息内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
default SmsResult<R> send(SmsBody msgBody, String... targets) {
if (targets == null) {
return failResult();
}
return send(msgBody, Arrays.asList(targets));
}
/**
* 异步发送消息
*
* @param msgBody 消息内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
SmsResult<R> sendAsync(SmsBody msgBody, Collection<String> targets);
/**
* 异步发送消息
*
* @param msgBody 消息内容
* @param target 发送对象
* @return 是否发送成功
*/
default SmsResult<R> sendAsync(SmsBody msgBody, String target) {
if (StringUtils.isBlank(target)) {
return failResult("target must not null.");
}
return sendAsync(msgBody, Collections.singletonList(target));
}
/**
* 异步发送消息
*
* @param msgBody 消息内容
* @param targets 发送对象列表
* @return 是否发送成功
*/
default SmsResult<R> sendAsync(SmsBody msgBody, String... targets) {
if (targets == null) {
return failResult("targets must not null.");
}
return sendAsync(msgBody, Arrays.asList(targets));
}
default SmsResult<R> failResult() {
SmsResult<R> resultBody = new SmsResult<>();
resultBody.setSuccess(false);
return resultBody;
}
default SmsResult<R> failResult(String message) {
SmsResult<R> resultBody = failResult();
resultBody.setMessage(message);
return resultBody;
}
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.dashboard.framework.sms;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.dashboard.common.exception.ServiceException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SMS_CHANNEL_NOT_INIT;
/**
* 抽象短信客户端工厂
*
* @author zzf
* @date 2021/1/28 14:01
*/
public class SmsClientAdapter {
private final Map<Long, SmsClient<?>> smsSenderMap;
public SmsClientAdapter(Map<Long, SmsClient<?>> smsSenderMap) {
if (ObjectUtil.isEmpty(smsSenderMap)) {
throw new ServiceException(SMS_CHANNEL_NOT_INIT);
}
this.smsSenderMap = smsSenderMap;
}
public void flushClient(Map<Long, SmsClient<?>> smsSenderMap) {
this.smsSenderMap.clear();
smsSenderMap.putAll(Collections.unmodifiableMap(smsSenderMap));
}
public SmsResult<?> send(Long channelId, SmsBody smsBody, Collection<String> targetPhone) {
SmsClient<?> smsClient = getSmsSender(channelId);
return smsClient.send(smsBody, targetPhone);
}
private SmsClient<?> getSmsSender(Long channelId) {
return smsSenderMap.get(channelId);
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.framework.msg.sms;
package cn.iocoder.dashboard.framework.sms;
import lombok.Data;

View File

@ -1,6 +0,0 @@
/**
* msg 专门专门用于发送消息的功能支撑上层的通用与核心业务
* 例如说短信邮件app通知等等
*
*/
package cn.iocoder.dashboard.modules.msg;

View File

@ -1,12 +1,12 @@
package cn.iocoder.dashboard.modules.msg.controller.sms;
package cn.iocoder.dashboard.modules.system.controller.sms;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.service.sms.SmsChannelService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms;
package cn.iocoder.dashboard.modules.system.controller.sms;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.CommonResult;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo;
package cn.iocoder.dashboard.modules.system.controller.sms.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo;
package cn.iocoder.dashboard.modules.system.controller.sms.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo.req;
package cn.iocoder.dashboard.modules.system.controller.sms.vo.req;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo.req;
package cn.iocoder.dashboard.modules.system.controller.sms.vo.req;
import cn.iocoder.dashboard.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
@ -7,11 +7,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("消息渠道分页 Request VO")
@Data

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp;
package cn.iocoder.dashboard.modules.system.controller.sms.vo.resp;
import io.swagger.annotations.ApiModel;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp;
package cn.iocoder.dashboard.modules.system.controller.sms.vo.resp;
import cn.iocoder.dashboard.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;

View File

@ -1,11 +1,11 @@
package cn.iocoder.dashboard.modules.msg.convert.sms;
package cn.iocoder.dashboard.modules.system.convert.sms;
import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.mapstruct.Mapper;

View File

@ -1,9 +1,9 @@
package cn.iocoder.dashboard.modules.msg.convert.sms;
package cn.iocoder.dashboard.modules.system.convert.sms;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsTemplateVO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsTemplateDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

View File

@ -1,10 +1,10 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsLog;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -1,7 +1,7 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsTemplateDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms;
package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;

View File

@ -77,9 +77,11 @@ public interface SysErrorCodeConstants {
// ========== 消息 1003001000 ==========
ErrorCode SMS_CHANNEL_NOT_FOUND = new ErrorCode(1003001001, "没有短信渠道信息, 请初始化sms_channel表数据。");
ErrorCode SMS_TEMPLATE_NOT_FOUND = new ErrorCode(1003001002, "没有短信模板信息, 请初始化sms_template表数据。");
ErrorCode SMS_SENDER_NOT_FOUND = new ErrorCode(1003001003, "没有找到对应的短信发送对象请检查sms_channel表和sms_template表数据");
ErrorCode INVALID_CHANNEL_CODE = new ErrorCode(1003001004, "非法的短信渠道code请检查sms_channel表的code值是否与SmsChannelEnum中的code值一致。");
ErrorCode SMS_CHANNEL_NOT_INIT = new ErrorCode(1003001001,
"短信渠道没有初始化, 请调用SmsClientWrapper#initSmsClient()或SmsClientWrapper#addSmsClient");
ErrorCode SMS_CHANNEL_NOT_FOUND = new ErrorCode(1003001002, "没有短信渠道信息, 请初始化sms_channel表数据。");
ErrorCode SMS_TEMPLATE_NOT_FOUND = new ErrorCode(1003001003, "没有短信模板信息, 请初始化sms_template表数据。");
ErrorCode SMS_SENDER_NOT_FOUND = new ErrorCode(1003001004, "没有找到对应的短信发送对象请检查sms_channel表和sms_template表数据");
ErrorCode INVALID_CHANNEL_CODE = new ErrorCode(1003001005, "非法的短信渠道code请检查sms_channel表的code值是否与SmsChannelEnum中的code值一致。");
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.dashboard.modules.msg.service.sms;
package cn.iocoder.dashboard.modules.system.service.sms;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import java.util.List;

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.service.sms;
package cn.iocoder.dashboard.modules.system.service.sms;
/**
* 短信渠道Service接口

View File

@ -1,4 +1,4 @@
package cn.iocoder.dashboard.modules.msg.service.sms;
package cn.iocoder.dashboard.modules.system.service.sms;
/**
* 短信渠道Service接口

View File

@ -1,21 +1,19 @@
package cn.iocoder.dashboard.modules.msg.service.sms.impl;
package cn.iocoder.dashboard.modules.system.service.sms.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.msg.sms.factory.AbstractSmsSenderFactory;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.msg.convert.sms.SmsChannelConvert;
import cn.iocoder.dashboard.modules.msg.convert.sms.SmsTemplateConvert;
import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsChannelMapper;
import cn.iocoder.dashboard.modules.msg.dal.mysql.dao.sms.SmsTemplateMapper;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.msg.dal.mysql.daoobject.sms.SmsTemplateDO;
import cn.iocoder.dashboard.modules.msg.service.sms.SmsChannelService;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
import cn.iocoder.dashboard.modules.system.convert.sms.SmsChannelConvert;
import cn.iocoder.dashboard.modules.system.convert.sms.SmsTemplateConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SmsChannelMapper;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SmsTemplateMapper;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsChannelDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsTemplateDO;
import cn.iocoder.dashboard.modules.system.service.sms.SmsChannelService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -76,12 +74,6 @@ public class SmsChannelServiceImpl implements SmsChannelService {
@Override
public boolean flushChannel() {
AbstractSmsSenderFactory smsSenderFactory = SpringUtil.getBean(AbstractSmsSenderFactory.class);
if (smsSenderFactory == null) {
return false;
}
smsSenderFactory.flush(listChannelAllEnabledInfo());
return true;
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.msg.service.sms.impl;
package cn.iocoder.dashboard.modules.system.service.sms.impl;
import cn.iocoder.dashboard.modules.msg.service.sms.SmsLogService;
import cn.iocoder.dashboard.modules.system.service.sms.SmsLogService;
import org.springframework.stereotype.Service;
/**

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.msg.service.sms.impl;
package cn.iocoder.dashboard.modules.system.service.sms.impl;
import cn.iocoder.dashboard.modules.msg.service.sms.SmsTemplateService;
import cn.iocoder.dashboard.modules.system.service.sms.SmsTemplateService;
import org.springframework.stereotype.Service;
/**

View File

@ -0,0 +1,34 @@
package cn.iocoder.dashboard.modules.system.sms;
import cn.iocoder.dashboard.framework.sms.SmsClient;
import cn.iocoder.dashboard.framework.sms.SmsClientAdapter;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.system.service.sms.SmsChannelService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 短信服务配置
*
* @author guer
*/
@Configuration
@ConditionalOnProperty("sms.enabled")
public class SmsConfiguration {
@Resource
private SmsChannelService channelService;
@Bean
public SmsClientAdapter smsClientWrapper() {
List<SmsChannelAllVO> smsChannelAllVOList = channelService.listChannelAllEnabledInfo();
Map<Long, SmsClient<?>> channelId2SmsClientMap = SmsSenderUtils.init(smsChannelAllVOList);
return new SmsClientAdapter(channelId2SmsClientMap);
}
}

View File

@ -0,0 +1,143 @@
package cn.iocoder.dashboard.modules.system.sms;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.dashboard.common.enums.SmsChannelEnum;
import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.framework.sms.SmsBody;
import cn.iocoder.dashboard.framework.sms.SmsClient;
import cn.iocoder.dashboard.framework.sms.SmsClientAdapter;
import cn.iocoder.dashboard.framework.sms.SmsResult;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO;
import cn.iocoder.dashboard.modules.system.sms.client.AliSmsClient;
import cn.iocoder.dashboard.modules.system.sms.proxy.SmsClientLogProxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
/**
* 短信发送者工厂
*
* @author zzf
* @date 2021/1/25 16:18
*/
public class SmsSenderUtils {
/**
* 短信渠道id:短信客户端map
* key: channelId
* val: SmsClient
*/
private static final Map<Long, SmsClient<?>> smsSenderMap = new ConcurrentHashMap<>(8);
/**
* 短信模板code: 短信渠道id map
* key: templateCode
* val: channelId
*/
private static final Map<String, Long> templateCode2ChannelIdMap = new HashMap<>();
/**
* 将短信渠道信息初始化成短信客户端
*
* @param smsChannelAllVOList 短信渠道信息
* @return 短信渠道id:短信客户端map
*/
public synchronized static Map<Long, SmsClient<?>> init(List<SmsChannelAllVO> smsChannelAllVOList) {
if (ObjectUtil.isEmpty(smsChannelAllVOList)) {
throw new ServiceException(SMS_CHANNEL_NOT_FOUND);
}
addSender(smsChannelAllVOList);
return smsSenderMap;
}
/**
* 重置短信客户端信息
*
* @param smsClientAdapter 短信客户端适配器
* @param smsChannelAllVOList 短信渠道信息集合
*/
public synchronized static void flush(SmsClientAdapter smsClientAdapter, List<SmsChannelAllVO> smsChannelAllVOList) {
smsSenderMap.clear();
smsClientAdapter.flushClient(init(smsChannelAllVOList));
}
/**
* 发送短信
*
* @param smsClientAdapter 短信客户端适配器
* @param smsBody 短信内容
* @param targetPhones 对象手机集合
* @return 短信发送结果
*/
public static SmsResult<?> send(SmsClientAdapter smsClientAdapter, SmsBody smsBody, Collection<String> targetPhones) {
Long channelId = templateCode2ChannelIdMap.get(smsBody.getCode());
if (channelId == null) {
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
return smsClientAdapter.send(channelId, smsBody, targetPhones);
}
/**
* 发送短信
*
* @param smsClientAdapter 短信客户端适配器
* @param smsBody 短信内容
* @param targetPhone 对象手机
* @return 短信发送结果
*/
public static SmsResult<?> send(SmsClientAdapter smsClientAdapter, SmsBody smsBody, String targetPhone) {
Long channelId = templateCode2ChannelIdMap.get(smsBody.getCode());
if (channelId == null) {
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
return smsClientAdapter.send(channelId, smsBody, Collections.singletonList(targetPhone));
}
/**
* 发送短信
*
* @param smsClientAdapter 短信客户端适配器
* @param smsBody 短信内容
* @param targetPhones 对象手机数组
* @return 短信发送结果
*/
public static SmsResult<?> send(SmsClientAdapter smsClientAdapter, SmsBody smsBody, String... targetPhones) {
Long channelId = templateCode2ChannelIdMap.get(smsBody.getCode());
if (channelId == null) {
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
return smsClientAdapter.send(channelId, smsBody, Arrays.asList(targetPhones));
}
private static void addSender(List<SmsChannelAllVO> smsChannelAllVOList) {
smsChannelAllVOList.forEach(channelAllVO -> addSender(SmsChannelEnum.getByCode(channelAllVO.getCode()), channelAllVO));
}
private static void addSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
if (channelEnum == null) {
throw new ServiceException(INVALID_CHANNEL_CODE);
}
List<SmsTemplateVO> templateList = channelAllVO.getTemplateList();
if (ObjectUtil.isEmpty(templateList)) {
throw new ServiceException(SMS_TEMPLATE_NOT_FOUND);
}
SmsClient<?> aliSmsClient = getSender(channelEnum, channelAllVO);
templateList.forEach(smsTemplateVO -> templateCode2ChannelIdMap.put(smsTemplateVO.getCode(), channelAllVO.getId()));
smsSenderMap.put(channelAllVO.getId(), aliSmsClient);
}
private static SmsClient<?> getSender(SmsChannelEnum channelEnum, SmsChannelAllVO channelAllVO) {
switch (channelEnum) {
case ALI:
return new SmsClientLogProxy<>(new AliSmsClient(channelAllVO));
// TODO fill more channel
default:
break;
}
throw new ServiceException(SMS_SENDER_NOT_FOUND);
}
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.dashboard.framework.msg.sms.impl.ali;
package cn.iocoder.dashboard.modules.system.sms.client;
import cn.iocoder.dashboard.framework.msg.sms.SmsBody;
import cn.iocoder.dashboard.framework.msg.sms.SmsResult;
import cn.iocoder.dashboard.framework.msg.sms.SmsSender;
import cn.iocoder.dashboard.modules.msg.controller.sms.vo.SmsChannelAllVO;
import cn.iocoder.dashboard.framework.sms.SmsBody;
import cn.iocoder.dashboard.framework.sms.SmsClient;
import cn.iocoder.dashboard.framework.sms.SmsResult;
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
@ -23,7 +23,7 @@ import java.util.Collection;
* @date 2021/1/25 14:17
*/
@Slf4j
public class AliSmsSender implements SmsSender<SendSmsResponse> {
public class AliSmsClient implements SmsClient<SendSmsResponse> {
private static final String OK = "OK";
@ -42,7 +42,7 @@ public class AliSmsSender implements SmsSender<SendSmsResponse> {
*
* @param channelVO 阿里云短信配置
*/
public AliSmsSender(SmsChannelAllVO channelVO) {
public AliSmsClient(SmsChannelAllVO channelVO) {
this.channelVO = channelVO;
@ -83,4 +83,9 @@ public class AliSmsSender implements SmsSender<SendSmsResponse> {
return failResult();
}
@Override
public SmsResult<SendSmsResponse> sendAsync(SmsBody msgBody, Collection<String> targets) {
return null;
}
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.dashboard.modules.system.sms.proxy;
import cn.iocoder.dashboard.framework.sms.SmsBody;
import cn.iocoder.dashboard.framework.sms.SmsClient;
import cn.iocoder.dashboard.framework.sms.SmsResult;
import cn.iocoder.dashboard.util.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Collection;
/**
* 消息父接口
*
* @author zzf
* @date 2021/1/22 15:46
*/
@Slf4j
public class SmsClientLogProxy<R> implements SmsClient<R> {
private final SmsClient<R> smsClient;
@Override
public SmsResult<R> send(SmsBody msgBody, Collection<String> targets) {
log.debug("ready send sms, body: {}, target: {}", JsonUtils.toJsonString(msgBody), targets);
SmsResult<R> resultBody = smsClient.send(msgBody, targets);
if (resultBody.getSuccess()) {
//
} else {
log.warn("send sms fail, body: {}, target: {}, resultBody: {}",
JsonUtils.toJsonString(msgBody),
targets,
JsonUtils.toJsonString(resultBody)
);
}
return resultBody;
}
@Override
public SmsResult<R> sendAsync(SmsBody msgBody, Collection<String> targets) {
return send(msgBody, targets);
}
public SmsClientLogProxy(SmsClient<R> smsClient) {
this.smsClient = smsClient;
}
}