diff --git a/sql/ruoyi-vue-pro.sql b/sql/ruoyi-vue-pro.sql index 9355fab64..af7f492f9 100644 --- a/sql/ruoyi-vue-pro.sql +++ b/sql/ruoyi-vue-pro.sql @@ -884,75 +884,4 @@ INSERT INTO `sys_user_role` VALUES (5, 100, 1, '', NULL, '', NULL, b'0'); INSERT INTO `sys_user_role` VALUES (6, 100, 2, '', NULL, '', NULL, b'0'); COMMIT; - --- ---------------------------- --- Table structure for sms_channel --- ---------------------------- -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', - `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 '签名值', - `remark` varchar(200) NOT NULL COMMENT '备注', - - `status` tinyint(4) NOT NULL default 0 COMMENT '启用状态(0正常 1停用)', - `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) DEFAULT '' COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信渠道'; -/* - 优先级值一样时,按照id顺序取值 -*/ - --- ---------------------------- --- Table structure for sms_template --- ---------------------------- -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 (对于前端来说就是绑定一个签名)', - `type` tinyint(4) NOT NULL default 1 COMMENT '消息类型 [0验证码 1短信通知 2推广短信 3国际/港澳台消息]', - `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 '参数数组(自动根据内容生成)', - `remark` varchar(200) NOT NULL COMMENT '备注', - - `status` tinyint(4) NOT NULL default 0 COMMENT '启用状态(0正常 1停用)', - `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) DEFAULT '' COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信模板'; - --- ---------------------------- --- Table structure for sms_log --- ---------------------------- -DROP TABLE IF EXISTS `sms_log`; -CREATE TABLE `sms_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号', - `channel_code` varchar(50) not null COMMENT '短信渠道编码(来自枚举类)', - `api_sms_id` varchar(50) not null COMMENT '实际渠道短信唯一标识', - `template_id` bigint(20) NOT NULL COMMENT '模板id', - `phone` char(11) not null COMMENT '手机号', - `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容', - `remark` varchar(200) NOT NULL COMMENT '备注', - `send_status` tinyint(4) NOT NULL default 0 COMMENT '发送状态(0发送中 1成功 2失败)', - `create_by` varchar(64) not null DEFAULT '' COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短信日志'; - SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/sms.sql b/sql/sms.sql new file mode 100644 index 000000000..82d854155 --- /dev/null +++ b/sql/sms.sql @@ -0,0 +1,82 @@ +/* + --2021.02.01 by fight, sms about table info +*/ + +-- ---------------------------- +-- Table structure for sms_channel +-- ---------------------------- +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', + `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 '签名值', + `remark` varchar(200) NOT NULL COMMENT '备注', + + `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '启用状态(0正常 1停用)', + `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信渠道'; +/* + 优先级值一样时,按照id顺序取值 +*/ + +-- ---------------------------- +-- Table structure for sms_template +-- ---------------------------- +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 (对于前端来说就是绑定一个签名)', + `type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '消息类型 [0验证码 1短信通知 2推广短信 3国际/港澳台消息]', + `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 '参数数组(自动根据内容生成)', + `remark` varchar(200) NOT NULL COMMENT '备注', + + `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '启用状态(0正常 1停用)', + `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `deleted` bit(1) DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信模板'; + +-- ---------------------------- +-- Table structure for sms_log +-- ---------------------------- +DROP TABLE IF EXISTS `sms_log`; +CREATE TABLE `sms_log` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号', + `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)', + `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id', + `template_code` varchar(50) NOT NULL COMMENT '渠道编码', + `phones` char(11) NOT NULL COMMENT '手机号(数组json字符串)', + `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容', + `remark` varchar(200) DEFAULT NULL COMMENT '备注', + `send_status` tinyint(4) NOT NULL DEFAULT 2 COMMENT '发送状态(1异步推送中 2发送中 3失败 4成功)', + `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信日志'; diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClient.java deleted file mode 100644 index bf1a44f57..000000000 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClient.java +++ /dev/null @@ -1,107 +0,0 @@ -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 { - - /** - * 发送消息 - * - * @param msgBody 消息内容 - * @param targets 发送对象列表 - * @return 是否发送成功 - */ - SmsResult send(SmsBody msgBody, Collection targets); - - /** - * 发送消息 - * - * @param msgBody 消息内容 - * @param target 发送对象 - * @return 是否发送成功 - */ - default SmsResult send(SmsBody msgBody, String target) { - if (StringUtils.isBlank(target)) { - return failResult(); - } - - return send(msgBody, Collections.singletonList(target)); - } - - /** - * 发送消息 - * - * @param msgBody 消息内容 - * @param targets 发送对象列表 - * @return 是否发送成功 - */ - default SmsResult send(SmsBody msgBody, String... targets) { - if (targets == null) { - return failResult(); - } - - return send(msgBody, Arrays.asList(targets)); - } - - - /** - * 异步发送消息 - * - * @param msgBody 消息内容 - * @param targets 发送对象列表 - * @return 是否发送成功 - */ - SmsResult sendAsync(SmsBody msgBody, Collection targets); - - /** - * 异步发送消息 - * - * @param msgBody 消息内容 - * @param target 发送对象 - * @return 是否发送成功 - */ - default SmsResult 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 sendAsync(SmsBody msgBody, String... targets) { - if (targets == null) { - return failResult("targets must not null."); - } - - return sendAsync(msgBody, Arrays.asList(targets)); - } - - default SmsResult failResult() { - SmsResult resultBody = new SmsResult<>(); - resultBody.setSuccess(false); - return resultBody; - } - - default SmsResult failResult(String message) { - SmsResult resultBody = failResult(); - resultBody.setMessage(message); - return resultBody; - } -} \ No newline at end of file diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClientAdapter.java b/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClientAdapter.java deleted file mode 100644 index 6edf7646f..000000000 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsClientAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -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> smsSenderMap; - - public SmsClientAdapter(Map> smsSenderMap) { - if (ObjectUtil.isEmpty(smsSenderMap)) { - throw new ServiceException(SMS_CHANNEL_NOT_INIT); - } - this.smsSenderMap = smsSenderMap; - } - - public void flushClient(Map> smsSenderMap) { - this.smsSenderMap.clear(); - smsSenderMap.putAll(Collections.unmodifiableMap(smsSenderMap)); - } - - public SmsResult send(Long channelId, SmsBody smsBody, Collection targetPhone) { - SmsClient smsClient = getSmsSender(channelId); - return smsClient.send(smsBody, targetPhone); - } - - private SmsClient getSmsSender(Long channelId) { - return smsSenderMap.get(channelId); - } -} diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java new file mode 100644 index 000000000..043dd2a50 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java @@ -0,0 +1,32 @@ +package cn.iocoder.dashboard.framework.sms.client; + +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; + +/** + * 抽象短息客户端 + * + * @author zzf + * @date 2021/2/1 9:28 + */ +public abstract class AbstractSmsClient implements SmsClient { + + /** + * 短信渠道参数 + */ + protected final SmsChannelPropertyVO channelVO; + + /** + * 构造阿里云短信发送处理 + * + * @param channelVO 阿里云短信配置 + */ + public AbstractSmsClient(SmsChannelPropertyVO channelVO) { + this.channelVO = channelVO; + } + + + public SmsChannelPropertyVO getProperty() { + return channelVO; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/sms/client/AliSmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java similarity index 73% rename from src/main/java/cn/iocoder/dashboard/modules/system/sms/client/AliSmsClient.java rename to src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java index f144ec380..39d359449 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/sms/client/AliSmsClient.java +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java @@ -1,9 +1,8 @@ -package cn.iocoder.dashboard.modules.system.sms.client; +package cn.iocoder.dashboard.framework.sms.client; -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 cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; @@ -23,7 +22,7 @@ import java.util.Collection; * @date 2021/1/25 14:17 */ @Slf4j -public class AliSmsClient implements SmsClient { +public class AliyunSmsClient extends AbstractSmsClient { private static final String OK = "OK"; @@ -33,8 +32,6 @@ public class AliSmsClient implements SmsClient { private static final String ENDPOINT = "cn-hangzhou"; - private final SmsChannelAllVO channelVO; - private final IAcsClient acsClient; /** @@ -42,9 +39,8 @@ public class AliSmsClient implements SmsClient { * * @param channelVO 阿里云短信配置 */ - public AliSmsClient(SmsChannelAllVO channelVO) { - - this.channelVO = channelVO; + public AliyunSmsClient(SmsChannelPropertyVO channelVO) { + super(channelVO); String accessKeyId = channelVO.getApiKey(); String accessKeySecret = channelVO.getApiSecret(); @@ -57,13 +53,13 @@ public class AliSmsClient implements SmsClient { @Override - public SmsResult send(SmsBody msgBody, Collection targets) { + public SmsResult send(SmsBody smsBody, Collection targets) { SendSmsRequest request = new SendSmsRequest(); request.setSysMethod(MethodType.POST); request.setPhoneNumbers(StringUtils.join(targets, ",")); request.setSignName(channelVO.getApiSignatureId()); - request.setTemplateCode(channelVO.getTemplateByTemplateCode(msgBody.getCode()).getApiTemplateId()); - request.setTemplateParam(msgBody.getParamsStr()); + request.setTemplateCode(channelVO.getTemplateByTemplateCode(smsBody.getTemplateCode()).getApiTemplateId()); + request.setTemplateParam(smsBody.getParamsStr()); try { SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); @@ -78,14 +74,15 @@ public class AliSmsClient implements SmsClient { return resultBody; } catch (Exception e) { log.debug(e.getMessage(), e); + return failResult("发送异常: " + e.getMessage()); } - - return failResult(); } - @Override - public SmsResult sendAsync(SmsBody msgBody, Collection targets) { - return null; + SmsResult failResult(String message) { + SmsResult resultBody = new SmsResult<>(); + resultBody.setSuccess(false); + resultBody.setMessage(message); + return resultBody; } } diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java new file mode 100644 index 000000000..290d21712 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java @@ -0,0 +1,25 @@ +package cn.iocoder.dashboard.framework.sms.client; + +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; + +import java.util.Collection; + +/** + * 短信父接口 + * + * @author zzf + * @date 2021/1/25 14:14 + */ +public interface SmsClient { + + /** + * 发送消息 + * + * @param smsBody 消息内容 + * @param targets 发送对象列表 + * @return 是否发送成功 + */ + SmsResult send(SmsBody smsBody, Collection targets); + +} \ No newline at end of file diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsBody.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java similarity index 67% rename from src/main/java/cn/iocoder/dashboard/framework/sms/SmsBody.java rename to src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java index 48fb2253b..9b132431e 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsBody.java +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java @@ -1,9 +1,10 @@ -package cn.iocoder.dashboard.framework.sms; +package cn.iocoder.dashboard.framework.sms.core; import cn.iocoder.dashboard.util.json.JsonUtils; import lombok.Data; import java.util.Map; +import java.util.UUID; /** * 消息内容实体类 @@ -11,10 +12,15 @@ import java.util.Map; @Data public class SmsBody { + /** + * 消息日志id + */ + private Long smsLogId; + /** * 模板编码 */ - private String code; + private String templateCode; /** * 参数列表 diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java new file mode 100644 index 000000000..6c1eaf07c --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java @@ -0,0 +1,71 @@ +package cn.iocoder.dashboard.framework.sms.core; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.dashboard.common.enums.SmsChannelEnum; +import cn.iocoder.dashboard.common.exception.ServiceException; +import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.client.AliyunSmsClient; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; + +/** + * 短信客户端工厂 + * + * @author zzf + * @date 2021/1/28 14:01 + */ +@Component +public class SmsClientFactory { + + private final Map> smsSenderMap = new ConcurrentHashMap<>(8); + + /** + * 创建短信客户端 + * + * @param propertyVO 参数对象 + * @return 客户端id(默认channelId) + */ + public Long createClient(SmsChannelPropertyVO propertyVO) { + if (StrUtil.isBlank(propertyVO.getCode())) { + throw ServiceExceptionUtil.exception(PARAM_VALUE_IS_NULL, "短信渠道编码"); + } + if (ObjectUtil.isNull(propertyVO.getId())) { + throw ServiceExceptionUtil.exception(PARAM_VALUE_IS_NULL, "短信渠道ID"); + } + + AbstractSmsClient sender = createClient(SmsChannelEnum.getByCode(propertyVO.getCode()), propertyVO); + smsSenderMap.put(propertyVO.getId(), sender); + return propertyVO.getId(); + } + + private AbstractSmsClient createClient(SmsChannelEnum channelEnum, SmsChannelPropertyVO channelVO) { + if (channelEnum == null) { + throw new ServiceException(INVALID_CHANNEL_CODE); + } + switch (channelEnum) { + case ALI: + return new AliyunSmsClient(channelVO); + // TODO fill more channel + default: + break; + } + throw new ServiceException(SMS_SENDER_NOT_FOUND); + } + + /** + * 获取短信客户端 + * + * @param channelId 渠道id + * @return 短信id + */ + public AbstractSmsClient getClient(Long channelId) { + return smsSenderMap.get(channelId); + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsResult.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java similarity index 86% rename from src/main/java/cn/iocoder/dashboard/framework/sms/SmsResult.java rename to src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java index b994514b6..a626bb759 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/SmsResult.java +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java @@ -1,4 +1,4 @@ -package cn.iocoder.dashboard.framework.sms; +package cn.iocoder.dashboard.framework.sms.core; import lombok.Data; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsChannelController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsChannelController.java index a75f9e2eb..932097514 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsChannelController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsChannelController.java @@ -44,11 +44,5 @@ public class SmsChannelController { return success(service.createChannel(reqVO)); } - @ApiOperation("刷新消息渠道信息") - @PutMapping("/flush") - public CommonResult flushChannel() { - return success(service.flushChannel()); - } - } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelAllVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelPropertyVO.java similarity index 94% rename from src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelAllVO.java rename to src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelPropertyVO.java index 042cddaa2..ab4d73594 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelAllVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsChannelPropertyVO.java @@ -14,7 +14,7 @@ import java.util.List; */ @Data @EqualsAndHashCode -public class SmsChannelAllVO implements Serializable { +public class SmsChannelPropertyVO implements Serializable { /** * id diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsTemplateVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsTemplateVO.java index c5156a6ae..d9daf7578 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsTemplateVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/SmsTemplateVO.java @@ -17,6 +17,7 @@ public class SmsTemplateVO { * 业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板) */ private String bizCode; + /** * 编码 */ @@ -27,4 +28,11 @@ public class SmsTemplateVO { */ private String apiTemplateId; + /** + * 内容 + */ + private String content; + + + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java b/src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java index af87d2253..985de7576 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java @@ -2,7 +2,7 @@ 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.system.controller.sms.vo.SmsChannelAllVO; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; 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; @@ -28,7 +28,7 @@ public interface SmsChannelConvert { List convertEnum(List bean); - List convert(List bean); + List convert(List bean); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SmsLogMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SmsLogMapper.java index a5dce01de..2e52b070e 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SmsLogMapper.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SmsLogMapper.java @@ -1,10 +1,10 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms; -import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsLog; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsLogDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper -public interface SmsLogMapper extends BaseMapper { +public interface SmsLogMapper extends BaseMapper { } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLog.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLogDO.java similarity index 73% rename from src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLog.java rename to src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLogDO.java index 169369b1e..10ffef478 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLog.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SmsLogDO.java @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Date; @@ -15,8 +16,9 @@ import java.util.Date; */ @Data @EqualsAndHashCode +@Accessors(chain = true) @TableName(value = "sms_log", autoResultMap = true) -public class SmsLog implements Serializable { +public class SmsLogDO implements Serializable { /** * 自增编号 @@ -29,19 +31,19 @@ public class SmsLog implements Serializable { private String channelCode; /** - * 实际渠道短信唯一标识 + * 短信渠道id */ - private String apiSmsId; + private Long channelId; /** * 模板id */ - private Long templateId; + private String templateCode; /** - * 手机号 + * 手机号(数组json字符串) */ - private String phone; + private String phones; /** * 内容 @@ -54,7 +56,7 @@ public class SmsLog implements Serializable { private String remark; /** - * 发送状态(0发送中 1成功 2失败) + * 发送状态(1异步推送中 2发送中 3失败 4成功) */ private Integer sendStatus; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java b/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java index 05e9e6393..a1fc081c5 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java @@ -83,5 +83,6 @@ public interface SysErrorCodeConstants { 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值一致。"); + ErrorCode PARAM_VALUE_IS_NULL = new ErrorCode(1003001006, "参数【{}】不能为空"); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java b/src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java new file mode 100644 index 000000000..426e4cb80 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java @@ -0,0 +1,30 @@ +package cn.iocoder.dashboard.modules.system.enums.sms; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 短信发送状态 + * + * @author zzf + * @date 2021/2/1 13:39 + */ +@Getter +@AllArgsConstructor +public enum SmsSendStatusEnum { + + //异步转发中 + ASYNC(1), + + //发送中 + SENDING(2), + + //失败 + FAIL(3), + + //成功 + SUCCESS(4); + + private final int status; + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java new file mode 100644 index 000000000..5da9703ca --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java @@ -0,0 +1,31 @@ +package cn.iocoder.dashboard.modules.system.mq.consumer.sms; + +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; +import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage; +import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage; +import cn.iocoder.dashboard.modules.system.service.sms.SmsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 针对 {@link SysDeptRefreshMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class SmsSendConsumer extends AbstractChannelMessageListener { + + @Resource + private SmsService smsService; + + @Override + public void onMessage(SmsSendMessage message) { + log.info("[onMessage][收到 发送短信 消息]"); + SmsResult send = smsService.send(message.getSmsBody(), message.getTargetPhones()); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SmsSendMessage.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SmsSendMessage.java new file mode 100644 index 000000000..8ca1207fa --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SmsSendMessage.java @@ -0,0 +1,25 @@ +package cn.iocoder.dashboard.modules.system.mq.message.sms; + +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import lombok.Data; + +import java.util.Collection; +import java.util.List; + +/** + * 部门数据刷新 Message + */ +@Data +public class SmsSendMessage implements ChannelMessage { + + private SmsBody smsBody; + + private List targetPhones; + + @Override + public String getChannel() { + return "sms.send"; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SmsProducer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SmsProducer.java new file mode 100644 index 000000000..892b333cf --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SmsProducer.java @@ -0,0 +1,31 @@ +package cn.iocoder.dashboard.modules.system.mq.producer.sms; + +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 短信的 Producer + */ +@Component +public class SmsProducer { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 发送 {@link SmsSendMessage} 消息 + */ + public void sendSmsSendMessage(SmsBody smsBody, List targetPhoneList) { + SmsSendMessage message = new SmsSendMessage(); + message.setSmsBody(smsBody); + message.setTargetPhones(targetPhoneList); + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsChannelService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsChannelService.java index 3040f3bdb..5fd39c2cf 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsChannelService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsChannelService.java @@ -1,7 +1,8 @@ package cn.iocoder.dashboard.modules.system.service.sms; import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO; +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; 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; @@ -17,18 +18,46 @@ import java.util.List; */ public interface SmsChannelService { + /** + * 初始化短信渠道 + */ + void initSmsClient(); + + /** + * 分页查询短信渠道信息 + * + * @param reqVO 参数对象 + * @return 短信渠道分页对象 + */ PageResult pageChannels(SmsChannelPageReqVO reqVO); + /** + * 创建新的渠道信息 + * + * @param reqVO 参数对象 + * @return 渠道id + */ Long createChannel(SmsChannelCreateReqVO reqVO); + /** + * 获取短信渠道枚举/渠道编码 + * + * @return 短信渠道枚举/渠道编码 + */ List getChannelEnums(); + /** + * 根据短信模板编码获取短信客户端 + * + * @param templateCode 短信模板编码 + * @return 短信客户端 + */ + AbstractSmsClient getClient(String templateCode); + /** * 查询渠道(包含名下模块)信息集合 * * @return 渠道(包含名下模块)信息集合 */ - List listChannelAllEnabledInfo(); - - boolean flushChannel(); + List listChannelAllEnabledInfo(); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsLogService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsLogService.java index d29d7f884..2646dcbfa 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsLogService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsLogService.java @@ -1,5 +1,11 @@ package cn.iocoder.dashboard.modules.system.service.sms; +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; + +import java.util.List; + /** * 短信渠道Service接口 * @@ -7,4 +13,23 @@ package cn.iocoder.dashboard.modules.system.service.sms; * @date 2021/1/25 9:24 */ public interface SmsLogService { + /** + * 发送短信前的日志处理 + * + * @param smsBody 短信内容 + * @param targetPhones 发送对象手机号集合 + * @param client 短信客户端 + * @param isAsync 是否异步发送 + * @return 生成的日志id + */ + Long beforeSendLog(SmsBody smsBody, List targetPhones, AbstractSmsClient client, Boolean isAsync); + + /** + * 发送消息后的日志处理 + * + * @param logId 日志id + * @param result 消息结果 + */ + void afterSendLog(Long logId, SmsResult result); + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsService.java new file mode 100644 index 000000000..56dc2dfa9 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SmsService.java @@ -0,0 +1,101 @@ +package cn.iocoder.dashboard.modules.system.service.sms; + +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 短信Service接口 + * + * @author zzf + * @date 2021/1/25 9:24 + */ +public interface SmsService { + + /** + * 发送消息 + * + * @param smsBody 消息内容 + * @param targetPhones 发送对象手机号列表 + * @return 是否发送成功 + */ + SmsResult send(SmsBody smsBody, List targetPhones); + + /** + * 发送消息 + * + * @param smsBody 消息内容 + * @param targetPhone 发送对象手机号 + * @return 是否发送成功 + */ + default SmsResult send(SmsBody smsBody, String targetPhone) { + if (StringUtils.isBlank(targetPhone)) { + return failResult("targetPhone must not null."); + } + + return send(smsBody, Collections.singletonList(targetPhone)); + } + + /** + * 发送消息 + * + * @param smsBody 消息内容 + * @param targetPhones 发送对象手机号数组 + * @return 是否发送成功 + */ + default SmsResult send(SmsBody smsBody, String... targetPhones) { + if (targetPhones == null) { + return failResult("targetPhones must not null."); + } + + return send(smsBody, Arrays.asList(targetPhones)); + } + + + /** + * 异步发送消息 + * + * @param msgBody 消息内容 + * @param targetPhones 发送对象列表 + */ + void sendAsync(SmsBody msgBody, List targetPhones); + + /** + * 异步发送消息 + * + * @param msgBody 消息内容 + * @param targetPhone 发送对象 + */ + default void sendAsync(SmsBody msgBody, String targetPhone) { + if (StringUtils.isBlank(targetPhone)) { + return; + } + sendAsync(msgBody, Collections.singletonList(targetPhone)); + } + + /** + * 异步发送消息 + * + * @param msgBody 消息内容 + * @param targetPhones 发送对象列表 + */ + default void sendAsync(SmsBody msgBody, String... targetPhones) { + if (targetPhones == null) { + return; + } + sendAsync(msgBody, Arrays.asList(targetPhones)); + } + + + default SmsResult failResult(String message) { + SmsResult resultBody = new SmsResult<>(); + resultBody.setSuccess(false); + resultBody.setMessage(message); + return resultBody; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsChannelServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsChannelServiceImpl.java index ccc47af3c..044a4e561 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsChannelServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsChannelServiceImpl.java @@ -3,7 +3,9 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.dashboard.common.enums.SmsChannelEnum; import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO; +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; 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; @@ -14,12 +16,16 @@ 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * 短信渠道Service实现类 @@ -30,6 +36,30 @@ import java.util.List; @Service public class SmsChannelServiceImpl implements SmsChannelService { + private final Map templateCode2ChannelIdMap = new ConcurrentHashMap<>(32); + + @Autowired + private SmsClientFactory smsClientFactory; + + /** + * 初始化短信客户端 + */ + @PostConstruct + @Override + public void initSmsClient() { + List smsChannelPropertyVOList = listChannelAllEnabledInfo(); + if (ObjectUtil.isEmpty(smsChannelPropertyVOList)) { + return; + } + smsChannelPropertyVOList.forEach(smsChannelPropertyVO -> { + Long clientId = smsClientFactory.createClient(smsChannelPropertyVO); + smsChannelPropertyVO.getTemplateList().forEach(smsTemplateVO -> { + templateCode2ChannelIdMap.put(smsTemplateVO.getCode(), clientId); + }); + }); + } + + @Resource private SmsChannelMapper mapper; @@ -54,12 +84,17 @@ public class SmsChannelServiceImpl implements SmsChannelService { } @Override - public List listChannelAllEnabledInfo() { + public AbstractSmsClient getClient(String templateCode) { + return smsClientFactory.getClient(templateCode2ChannelIdMap.get(templateCode)); + } + + @Override + public List listChannelAllEnabledInfo() { List channelDOList = mapper.selectEnabledList(); if (ObjectUtil.isNull(channelDOList)) { return null; } - List channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList); + List channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList); channelAllVOList.forEach(smsChannelDO -> { @@ -71,10 +106,4 @@ public class SmsChannelServiceImpl implements SmsChannelService { }); return channelAllVOList; } - - @Override - public boolean flushChannel() { - - return true; - } } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsLogServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsLogServiceImpl.java index e54d265c6..651917259 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsLogServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsLogServiceImpl.java @@ -1,8 +1,21 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl; +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelPropertyVO; +import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO; +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SmsLogMapper; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SmsLogDO; +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum; import cn.iocoder.dashboard.modules.system.service.sms.SmsLogService; +import cn.iocoder.dashboard.util.json.JsonUtils; +import cn.iocoder.dashboard.util.string.StrUtils; import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.util.List; + /** * 短信日志Service实现类 * @@ -12,4 +25,48 @@ import org.springframework.stereotype.Service; @Service public class SmsLogServiceImpl implements SmsLogService { + @Resource + private SmsLogMapper smsLogMapper; + + @Override + public Long beforeSendLog(SmsBody smsBody, List targetPhones, AbstractSmsClient client, Boolean isAsync) { + SmsLogDO smsLog = new SmsLogDO(); + if (smsBody.getSmsLogId() != null) { + smsLog.setId(smsBody.getSmsLogId()); + smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus()); + smsLogMapper.updateById(smsLog); + return smsBody.getSmsLogId(); + } else { + SmsChannelPropertyVO property = client.getProperty(); + SmsTemplateVO smsTemplate = property.getTemplateByTemplateCode(smsBody.getTemplateCode()); + + smsLog.setChannelCode(property.getCode()) + .setChannelId(property.getId()) + .setTemplateCode(smsTemplate.getCode()) + .setPhones(JsonUtils.toJsonString(targetPhones)) + .setContent(StrUtils.replace(smsTemplate.getContent(), smsBody.getParams())); + + if (isAsync) { + smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus()); + } else { + smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus()); + } + smsLogMapper.insert(smsLog); + return smsLog.getId(); + } + } + + @Override + public void afterSendLog(Long logId, SmsResult result) { + SmsLogDO smsLog = new SmsLogDO(); + smsLog.setId(logId); + if (result.getSuccess()) { + smsLog.setSendStatus(SmsSendStatusEnum.SUCCESS.getStatus()); + } else { + smsLog.setSendStatus(SmsSendStatusEnum.FAIL.getStatus()); + smsLog.setRemark(result.getMessage() + JsonUtils.toJsonString(result.getResult())); + } + smsLogMapper.updateById(smsLog); + } + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsServiceImpl.java new file mode 100644 index 000000000..4dc090242 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SmsServiceImpl.java @@ -0,0 +1,51 @@ +package cn.iocoder.dashboard.modules.system.service.sms.impl; + +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.core.SmsBody; +import cn.iocoder.dashboard.framework.sms.core.SmsResult; +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SmsProducer; +import cn.iocoder.dashboard.modules.system.service.sms.SmsChannelService; +import cn.iocoder.dashboard.modules.system.service.sms.SmsLogService; +import cn.iocoder.dashboard.modules.system.service.sms.SmsService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 短信日志Service实现类 + * + * @author zzf + * @date 2021/1/25 9:25 + */ +@Service +public class SmsServiceImpl implements SmsService { + + @Resource + private SmsChannelService channelService; + + @Resource + private SmsLogService smsLogService; + + @Resource + private SmsProducer smsProducer; + + @Override + public SmsResult send(SmsBody smsBody, List targetPhones) { + AbstractSmsClient client = channelService.getClient(smsBody.getTemplateCode()); + Long logId = smsLogService.beforeSendLog(smsBody, targetPhones, client, false); + + SmsResult result = client.send(smsBody, targetPhones); + + smsLogService.afterSendLog(logId, result); + + return result; + } + + @Override + public void sendAsync(SmsBody smsBody, List targetPhones) { + AbstractSmsClient client = channelService.getClient(smsBody.getTemplateCode()); + smsLogService.beforeSendLog(smsBody, targetPhones, client, true); + smsProducer.sendSmsSendMessage(smsBody, targetPhones); + } +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsConfiguration.java b/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsConfiguration.java deleted file mode 100644 index ffb5887bd..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsConfiguration.java +++ /dev/null @@ -1,34 +0,0 @@ -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 smsChannelAllVOList = channelService.listChannelAllEnabledInfo(); - Map> channelId2SmsClientMap = SmsSenderUtils.init(smsChannelAllVOList); - return new SmsClientAdapter(channelId2SmsClientMap); - } - -} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsSenderUtils.java b/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsSenderUtils.java deleted file mode 100644 index ea9c24489..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/sms/SmsSenderUtils.java +++ /dev/null @@ -1,143 +0,0 @@ -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> smsSenderMap = new ConcurrentHashMap<>(8); - - /** - * 短信模板code: 短信渠道id map - * key: templateCode - * val: channelId - */ - private static final Map templateCode2ChannelIdMap = new HashMap<>(); - - /** - * 将短信渠道信息初始化成短信客户端 - * - * @param smsChannelAllVOList 短信渠道信息 - * @return 短信渠道id:短信客户端map - */ - public synchronized static Map> init(List 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 smsChannelAllVOList) { - smsSenderMap.clear(); - smsClientAdapter.flushClient(init(smsChannelAllVOList)); - } - - /** - * 发送短信 - * - * @param smsClientAdapter 短信客户端适配器 - * @param smsBody 短信内容 - * @param targetPhones 对象手机集合 - * @return 短信发送结果 - */ - public static SmsResult send(SmsClientAdapter smsClientAdapter, SmsBody smsBody, Collection 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 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 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); - } -} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/sms/proxy/SmsClientLogProxy.java b/src/main/java/cn/iocoder/dashboard/modules/system/sms/proxy/SmsClientLogProxy.java deleted file mode 100644 index a7d201e6f..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/sms/proxy/SmsClientLogProxy.java +++ /dev/null @@ -1,48 +0,0 @@ -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 implements SmsClient { - - private final SmsClient smsClient; - - @Override - public SmsResult send(SmsBody msgBody, Collection targets) { - log.debug("ready send sms, body: {}, target: {}", JsonUtils.toJsonString(msgBody), targets); - - SmsResult 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 sendAsync(SmsBody msgBody, Collection targets) { - return send(msgBody, targets); - } - - public SmsClientLogProxy(SmsClient smsClient) { - this.smsClient = smsClient; - } -} diff --git a/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java b/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java index 5e98d915b..e0ca605a4 100644 --- a/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java +++ b/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java @@ -1,7 +1,10 @@ package cn.iocoder.dashboard.util.string; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import java.util.Map; + /** * 字符串工具类 * @@ -13,4 +16,22 @@ public class StrUtils { return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好 } + /** + * 指定字符串的 + * @param str + * @param replaceMap + * @return + */ + public static String replace(String str, Map replaceMap) { + assert StrUtil.isNotBlank(str); + if (ObjectUtil.isEmpty(replaceMap)) { + return str; + } + String result = null; + for (String key : replaceMap.keySet()) { + result = str.replace(key, replaceMap.get(key)); + } + return result; + } + }