Browse Source

短信提交 2021-03-28,增加发送日志

YunaiV 4 years ago
parent
commit
515fca5c41
34 changed files with 575 additions and 961 deletions
  1. 3 0
      pom.xml
  2. 0 67
      src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java
  3. 21 0
      src/main/java/cn/iocoder/dashboard/framework/sms/config/SmsConfiguration.java
  4. 0 38
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java
  5. 0 133
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java
  6. 1 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsConstants.java
  7. 48 16
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java
  8. 15 7
      src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java
  9. 28 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClientFactory.java
  10. 89 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
  11. 83 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java
  12. 38 42
      src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/ali/AliyunSmsClient.java
  13. 49 60
      src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/yunpian/YunpianSmsClient.java
  14. 11 2
      src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsSendFailureTypeEnum.java
  15. 52 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperties.java
  16. 0 68
      src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperty.java
  17. 0 47
      src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsTemplateProperty.java
  18. 0 3
      src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/req/SmsChannelPageReqVO.java
  19. 3 10
      src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java
  20. 0 3
      src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsTemplateConvert.java
  21. 22 34
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsChannelDO.java
  22. 10 3
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsSendLogDO.java
  23. 0 66
      src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsSendLogDOX.java
  24. 10 14
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java
  25. 0 34
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsQueryLogMapper.java
  26. 2 3
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsSendLogMapper.java
  27. 0 23
      src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java
  28. 0 24
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelService.java
  29. 0 35
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java
  30. 27 5
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsSendLogService.java
  31. 28 59
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java
  32. 0 63
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsQueryLogServiceImpl.java
  33. 8 83
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsSendLogServiceImpl.java
  34. 27 19
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java

+ 3 - 0
pom.xml

@@ -46,6 +46,7 @@
         <easyexcel.verion>2.2.7</easyexcel.verion>
         <velocity.version>2.2</velocity.version>
         <screw.version>1.0.5</screw.version>
+        <!-- 三方云服务相关 -->
     </properties>
 
     <!-- 依赖声明 -->
@@ -271,6 +272,8 @@
             <version>${screw.version}</version>
         </dependency>
 
+        <!-- 三方云服务相关 -->
+
         <!-- SMS SDK begin -->
         <dependency>
             <groupId>com.yunpian.sdk</groupId>

+ 0 - 67
src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java

@@ -1,67 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.client;
-
-import cn.iocoder.dashboard.framework.sms.core.SmsBody;
-import cn.iocoder.dashboard.framework.sms.core.SmsResult;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * 抽象短息客户端
- *
- * @author zzf
- * @date 2021/2/1 9:28
- */
-@Slf4j
-public abstract class AbstractSmsClient implements SmsClient {
-
-    /**
-     * 短信渠道参数
-     */
-    protected final SmsChannelProperty channelVO;
-
-    /**
-     * 短信客户端有参构造函数
-     *
-     * @param property 短信配置
-     */
-    public AbstractSmsClient(SmsChannelProperty property) {
-        this.channelVO = property;
-    }
-
-    public SmsChannelProperty getProperty() {
-        return channelVO;
-    }
-
-    @Override
-    public final SmsResult send(String templateApiId, SmsBody smsBody, String target) {
-        SmsResult result;
-        try {
-            beforeSend(templateApiId, smsBody, target);
-            result = doSend(templateApiId, smsBody, target);
-            afterSend(templateApiId, smsBody, target, result);
-        } catch (Exception e) {
-            // exception handle
-            log.debug(e.getMessage(), e);
-            return SmsResult.failResult("发送异常: " + e.getMessage());
-        }
-        return result;
-    }
-
-    /**
-     * 发送消息
-     *
-     * @param templateApiId 短信模板唯一标识
-     * @param smsBody       消息内容
-     * @param targetPhone   发送对象手机号
-     * @return 短信发送结果
-     * @throws Exception 调用发送失败,抛出异常
-     */
-    protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception;
-
-    protected void beforeSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
-    }
-
-    protected void afterSend(String templateApiId, SmsBody smsBody, String targetPhone, SmsResult result) throws Exception {
-    }
-
-}

+ 21 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/config/SmsConfiguration.java

@@ -0,0 +1,21 @@
+package cn.iocoder.dashboard.framework.sms.config;
+
+import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
+import cn.iocoder.dashboard.framework.sms.core.client.impl.SmsClientFactoryImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 短信配置类
+ *
+ * @author 芋道源码
+ */
+@Configuration
+public class SmsConfiguration {
+
+    @Bean
+    public SmsClientFactory smsClientFactory() {
+        return new SmsClientFactoryImpl();
+    }
+
+}

+ 0 - 38
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java

@@ -1,38 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.core;
-
-import cn.iocoder.dashboard.util.json.JsonUtils;
-import lombok.Data;
-
-import java.util.Map;
-
-/**
- * 消息内容实体类
- */
-@Data
-public class SmsBody {
-
-    /**
-     * 消息日志id
-     */
-    private Long smsLogId;
-
-    /**
-     * 模板编码
-     */
-    private String templateCode;
-
-    /**
-     * 模板编码
-     */
-    private String templateContent;
-
-    /**
-     * 参数列表
-     */
-    private Map<String, String> params;
-
-    public String getParamsStr() {
-        return JsonUtils.toJsonString(params);
-    }
-
-}

+ 0 - 133
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java

@@ -1,133 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.core;
-
-import cn.iocoder.dashboard.common.exception.ServiceException;
-import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
-import cn.iocoder.dashboard.framework.sms.client.impl.ali.AliyunSmsClient;
-import cn.iocoder.dashboard.framework.sms.client.impl.yunpian.YunpianSmsClient;
-import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
-import cn.iocoder.dashboard.util.json.JsonUtils;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.ServletRequest;
-import java.util.Collection;
-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 {
-
-    /**
-     * channelId: client map
-     * 保存 渠道id: 对应短信客户端 的map
-     */
-    private final Map<Long, AbstractSmsClient> smsSenderMap = new ConcurrentHashMap<>(8);
-
-    /**
-     * templateCode: TemplateProperty map
-     * 保存 模板编码:模板信息 的map
-     */
-    private final Map<String, SmsTemplateProperty> templatePropertyMap = new ConcurrentHashMap<>(16);
-
-    /**
-     * 创建短信客户端
-     *
-     * @param propertyVO 参数对象
-     * @return 客户端id(默认channelId)
-     */
-    public Long createClient(SmsChannelProperty propertyVO) {
-        AbstractSmsClient sender = createClient(SmsChannelEnum.getByCode(propertyVO.getCode()), propertyVO);
-        smsSenderMap.put(propertyVO.getId(), sender);
-        return propertyVO.getId();
-    }
-
-    private AbstractSmsClient createClient(SmsChannelEnum channelEnum, SmsChannelProperty channelVO) {
-        if (channelEnum == null) {
-            throw new ServiceException(INVALID_CHANNEL_CODE);
-        }
-        switch (channelEnum) {
-            case ALIYUN:
-                return new AliyunSmsClient(channelVO);
-            case YUN_PIAN:
-                return new YunpianSmsClient(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);
-    }
-
-
-    /**
-     * 添加或修改短信模板信息缓存
-     */
-    public void addOrUpdateTemplateCache(Collection<SmsTemplateProperty> templateProperties) {
-        templateProperties.forEach(this::addOrUpdateTemplateCache);
-    }
-
-
-    /**
-     * 添加或修改短信模板信息缓存
-     */
-    public void addOrUpdateTemplateCache(SmsTemplateProperty templateProperty) {
-        templatePropertyMap.put(templateProperty.getCode(), templateProperty);
-    }
-
-
-    /**
-     * 根据短信模板编码获取模板唯一标识
-     *
-     * @param templateCode 短信模板编码
-     * @return 短信id
-     */
-    public String getTemplateApiIdByCode(String templateCode) {
-        SmsTemplateProperty smsTemplateProperty = templatePropertyMap.get(templateCode);
-        if (smsTemplateProperty == null) {
-            throw new ServiceException(SMS_TEMPLATE_NOT_EXISTS);
-        }
-        return smsTemplateProperty.getApiTemplateId();
-    }
-
-
-    /**
-     * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId
-     *
-     * @param callbackRequest 短信发送回调函数请求
-     * @return 第三方平台短信唯一标识
-     */
-    public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {
-        for (Long channelId : smsSenderMap.keySet()) {
-            AbstractSmsClient smsClient = smsSenderMap.get(channelId);
-            try {
-                SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);
-                if (smsSendResult != null) {
-                    return smsSendResult;
-                }
-            } catch (Exception ignored) {
-            }
-        }
-        throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "
-                + JsonUtils.toJsonString(callbackRequest.getParameterMap()));
-    }
-
-
-}

+ 1 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsConstants.java

@@ -15,4 +15,5 @@ public interface SmsConstants {
     String COMMA = ",";
 
     String SUCCESS = "SUCCESS";
+
 }

+ 48 - 16
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java

@@ -1,7 +1,8 @@
 package cn.iocoder.dashboard.framework.sms.core;
 
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
 import lombok.Data;
-import lombok.experimental.Accessors;
 
 import java.io.Serializable;
 
@@ -9,33 +10,64 @@ import java.io.Serializable;
  * 消息内容实体类
  */
 @Data
-@Accessors(chain = true)
 public class SmsResult implements Serializable {
 
     /**
-     * 是否成功(发送短信的请求是否成功)
+     * 是否成功
+     *
+     * 注意,是调用 API 短信平台的请求是否成功
      */
     private Boolean success;
-
     /**
-     * 第三方唯一标识
+     * 发送失败的类型
+     *
+     * 枚举 {@link SmsSendFailureTypeEnum#getType()}
      */
-    private String apiId;
-
+    private Integer sendFailureType;
     /**
-     * 状态码
+     * 发送失败的提示
+     *
+     * 一般情况下,使用 {@link SmsSendFailureTypeEnum#getMsg()}
+     * 异常情况下,通过格式化 Exception 的提示存储
      */
-    private String code;
+    private String sendFailureMsg;
 
     /**
-     * 提示
+     * 短信 API 发送的错误码
+     *
+     * 由于第三方的错误码可能是字符串,所以使用 String 类型
+     */
+    private String apiSendCode;
+    /**
+     * 短信 API 发送的提示
      */
-    private String message;
+    private String apiSendMsg;
+    /**
+     * 短信 API 发送返回的唯一请求 ID
+     *
+     * 用于和短信 API 进行定位于排错
+     */
+    private String apiRequestId;
+    /**
+     * 短信 API 发送返回的序号
+     *
+     * 用于和短信 API 平台的发送记录关联
+     */
+    private String apiSerialNo;
+
+    private SmsResult() {
+    }
 
-    public static SmsResult failResult(String message) {
-        SmsResult resultBody = new SmsResult();
-        resultBody.setSuccess(false);
-        resultBody.setMessage(message);
-        return resultBody;
+    public static SmsResult success(SmsSendFailureTypeEnum sendFailureType,
+                                    String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo) {
+        return new SmsResult().setSuccess(true).setSendFailureType(sendFailureType.getType()).setSendFailureMsg(sendFailureType.getMsg())
+                .setApiSendCode(apiSendCode).setApiSendMsg(apiSendMsg).setApiRequestId(apiRequestId).setApiSerialNo(apiSerialNo);
     }
+
+    public static SmsResult error(Throwable ex) {
+        return new SmsResult().setSuccess(false)
+                .setSendFailureType(SmsSendFailureTypeEnum.SMS_SEND_EXCEPTION.getType())
+                .setSendFailureMsg(ExceptionUtil.getRootCauseMessage(ex));
+    }
+
 }

+ 15 - 7
src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java

@@ -1,13 +1,13 @@
-package cn.iocoder.dashboard.framework.sms.client;
+package cn.iocoder.dashboard.framework.sms.core.client;
 
-import cn.iocoder.dashboard.framework.sms.core.SmsBody;
 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
 
 import javax.servlet.ServletRequest;
+import java.util.Map;
 
 /**
- * 短信接口
+ * 短信客户端接口
  *
  * @author zzf
  * @date 2021/1/25 14:14
@@ -15,14 +15,22 @@ import javax.servlet.ServletRequest;
 public interface SmsClient {
 
     /**
+     * 获得渠道编号
+     *
+     * @return 渠道编号
+     */
+    Long getId();
+
+    /**
      * 发送消息
      *
-     * @param templateApiId 短信模板唯一标识
-     * @param smsBody       消息内容
-     * @param targets       发送对象列表
+     * @param sendLogId 发送日志编号
+     * @param mobile 手机号
+     * @param apiTemplateId 短信 API 的模板编号
+     * @param templateParams 短信模板参数
      * @return 短信发送结果
      */
-    SmsResult send(String templateApiId, SmsBody smsBody, String targets);
+    SmsResult send(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams);
 
     // TODO FROM 芋艿 to ZZF:是不是可以改成意图更明确的解析返回结果,例如说 parseXXXX
     /**

+ 28 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClientFactory.java

@@ -0,0 +1,28 @@
+package cn.iocoder.dashboard.framework.sms.core.client;
+
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+
+/**
+ * 短信客户端工厂接口
+ *
+ * @author zzf
+ * @date 2021/1/28 14:01
+ */
+public interface SmsClientFactory {
+
+    /**
+     * 获得短信 Client
+     *
+     * @param channelId 渠道编号
+     * @return 短信 Client
+     */
+    SmsClient getSmsClient(Long channelId);
+
+    /**
+     * 创建短信 Client
+     *
+     * @param properties 配置对象
+     */
+    void createOrUpdateSmsClient(SmsChannelProperties properties);
+
+}

+ 89 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java

@@ -0,0 +1,89 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl;
+
+import cn.iocoder.dashboard.framework.sms.core.SmsResult;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+
+/**
+ * 短信客户端抽象类
+ *
+ * @author zzf
+ * @date 2021/2/1 9:28
+ */
+@Slf4j
+public abstract class AbstractSmsClient implements SmsClient {
+
+    /**
+     * 短信渠道配置
+     */
+    protected volatile SmsChannelProperties properties;
+
+    /**
+     * 短信客户端有参构造函数
+     *
+     * @param properties 短信配置
+     */
+    public AbstractSmsClient(SmsChannelProperties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * 初始化
+     */
+    public final void init() {
+        doInit();
+        log.info("[init][配置({}) 初始化完成]", properties);
+    }
+
+    public final void refresh(SmsChannelProperties properties) {
+        // 判断是否更新
+        if (!properties.equals(this.properties)) {
+            return;
+        }
+        log.info("[refresh][配置({})发生变化,重新初始化]", properties);
+        this.properties = properties;
+        // 初始化
+        this.init();
+    }
+
+    /**
+     * 自定义初始化
+     */
+    protected abstract void doInit();
+
+    @Override
+    public Long getId() {
+        return properties.getId();
+    }
+
+    @Override
+    public final SmsResult send(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) {
+        SmsResult result;
+        try {
+            result = doSend(sendLogId, mobile, apiTemplateId, templateParams);
+        } catch (Throwable ex) {
+            // 打印异常日志
+            log.error("[send][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
+                    sendLogId, mobile, apiTemplateId, templateParams, ex);
+            // 封装返回
+            return SmsResult.error(ex);
+        }
+        return result;
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param sendLogId 发送日志编号
+     * @param mobile 手机号
+     * @param apiTemplateId 短信 API 的模板编号
+     * @param templateParams 短信模板参数
+     * @return 短信发送结果
+     */
+    protected abstract SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams)
+            throws Throwable;
+
+}

+ 83 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java

@@ -0,0 +1,83 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl;
+
+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.impl.aliyun.AliyunSmsClient;
+import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.Assert;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 短信客户端工厂接口
+ *
+ * @author zzf
+ */
+@Validated
+@Slf4j
+public class SmsClientFactoryImpl implements SmsClientFactory {
+
+    /**
+     * 短信客户端 Map
+     * key:渠道编号,使用 {@link SmsChannelProperties#getId()}
+     */
+    private final Map<Long, AbstractSmsClient> clients = new ConcurrentHashMap<>();
+
+    @Override
+    public SmsClient getSmsClient(Long channelId) {
+        return clients.get(channelId);
+    }
+
+    @Override
+    public void createOrUpdateSmsClient(SmsChannelProperties properties) {
+        AbstractSmsClient client = clients.get(properties.getId());
+        if (client == null) {
+            client = this.createSmsClient(properties);
+            clients.put(client.getId(), client);
+        } else {
+            client.refresh(properties);
+        }
+    }
+
+    private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {
+        SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode());
+        Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
+        // 创建客户端
+        switch (channelEnum) {
+            case ALIYUN:
+                return new AliyunSmsClient(properties);
+            case YUN_PIAN:
+                return new YunpianSmsClient(properties);
+        }
+        // 创建失败,错误日志 + 抛出异常
+        log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
+        throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
+    }
+
+//    /**
+//     * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId
+//     *
+//     * @param callbackRequest 短信发送回调函数请求
+//     * @return 第三方平台短信唯一标识
+//     */
+//    public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {
+//        for (Long channelId : clients.keySet()) {
+//            AbstractSmsClient smsClient = clients.get(channelId);
+//            try {
+//                SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);
+//                if (smsSendResult != null) {
+//                    return smsSendResult;
+//                }
+//            } catch (Exception ignored) {
+//            }
+//        }
+//        throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "
+//                + JsonUtils.toJsonString(callbackRequest.getParameterMap()));
+//    }
+
+}

+ 38 - 42
src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/ali/AliyunSmsClient.java

@@ -1,19 +1,15 @@
-package cn.iocoder.dashboard.framework.sms.client.impl.ali;
+package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
-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.framework.sms.core.SmsResultDetail;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
+import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
 import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
 import cn.iocoder.dashboard.util.json.JsonUtils;
 import com.aliyuncs.DefaultAcsClient;
 import com.aliyuncs.IAcsClient;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
-import com.aliyuncs.http.MethodType;
 import com.aliyuncs.profile.DefaultProfile;
 import com.aliyuncs.profile.IClientProfile;
 import com.fasterxml.jackson.core.type.TypeReference;
@@ -28,7 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 阿里短信实现类
+ * 阿里短信客户端的实现类
  *
  * @author zzf
  * @date 2021/1/25 14:17
@@ -36,54 +32,54 @@ import java.util.Map;
 @Slf4j
 public class AliyunSmsClient extends AbstractSmsClient {
 
-    private static final String OK = "OK";
-
     private static final String PRODUCT = "Dystopi";
-
     private static final String DOMAIN = "dysmsapi.aliyuncs.com";
-
     private static final String ENDPOINT = "cn-hangzhou";
 
-    private final IAcsClient acsClient;
+    private static final String OK = "OK";
 
     /**
-     * 构造阿里云短信发送处理
-     *
-     * @param channelVO 阿里云短信配置
+     * 阿里云客户端
      */
-    public AliyunSmsClient(SmsChannelProperty channelVO) {
-        super(channelVO);
+    private volatile IAcsClient acsClient;
 
-        String accessKeyId = channelVO.getApiKey();
-        String accessKeySecret = channelVO.getApiSecret();
+    public AliyunSmsClient(SmsChannelProperties properties) {
+        super(properties);
+    }
 
-        IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, accessKeyId, accessKeySecret);
+    @Override
+    protected void doInit() {
+        IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
         DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
-
         acsClient = new DefaultAcsClient(profile);
     }
 
     @Override
-    public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
-        SendSmsRequest request = new SendSmsRequest();
-        request.setSysMethod(MethodType.POST);
-        request.setPhoneNumbers(targetPhone);
-        request.setSignName(channelVO.getApiSignatureId());
-        request.setTemplateCode(templateApiId);
-        request.setTemplateParam(smsBody.getParamsStr());
-        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
-
-        boolean success = OK.equals(sendSmsResponse.getCode());
-        if (!success) {
-            log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
-        }
-        return new SmsResult()
-                .setSuccess(success)
-                .setMessage(sendSmsResponse.getMessage())
-                .setCode(sendSmsResponse.getCode())
-                .setApiId(sendSmsResponse.getBizId());
+    protected SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) throws Exception {
+        return null;
     }
 
+//    @Override
+//    public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
+//        SendSmsRequest request = new SendSmsRequest();
+//        request.setSysMethod(MethodType.POST);
+//        request.setPhoneNumbers(targetPhone);
+//        request.setSignName(properties.getSignature());
+//        request.setTemplateCode(templateApiId);
+//        request.setTemplateParam(smsBody.getParamsStr());
+//        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
+//
+//        boolean success = OK.equals(sendSmsResponse.getCode());
+//        if (!success) {
+//            log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
+//        }
+//        return new SmsResult()
+//                .setSuccess(success)
+//                .setMessage(sendSmsResponse.getMessage())
+//                .setCode(sendSmsResponse.getCode())
+//                .setApiId(sendSmsResponse.getBizId());
+//    }
+
     /**
      * [{
      * "send_time" : "2017-08-30 00:00:00",
@@ -131,8 +127,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
 
         public Integer getSendStatus() {
             return ((Boolean) sendResultParamMap.get(CallbackField.SUCCESS))
-                    ? SysSmsSendStatusEnum.SEND_SUCCESS.getStatus()
-                    : SysSmsSendStatusEnum.SEND_FAIL.getStatus();
+                    ? SysSmsSendStatusEnum.SUCCESS.getStatus()
+                    : SysSmsSendStatusEnum.FAILURE.getStatus();
         }
 
         public String getBizId() {

+ 49 - 60
src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/yunpian/YunpianSmsClient.java

@@ -1,19 +1,20 @@
-package cn.iocoder.dashboard.framework.sms.client.impl.yunpian;
+package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.CharsetUtil;
-import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
-import cn.iocoder.dashboard.framework.sms.core.SmsBody;
+import cn.hutool.core.util.URLUtil;
 import cn.iocoder.dashboard.framework.sms.core.SmsConstants;
 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
+import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
 import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
 import cn.iocoder.dashboard.util.json.JsonUtils;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.yunpian.sdk.YunpianClient;
-import com.yunpian.sdk.constant.Code;
 import com.yunpian.sdk.constant.YunpianConstant;
 import com.yunpian.sdk.model.Result;
 import com.yunpian.sdk.model.SmsSingleSend;
@@ -25,9 +26,10 @@ import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.StringJoiner;
 
 /**
- * 云片短信实现类
+ * 云片短信客户端的实现类
  *
  * @author zzf
  * @date 9:48 2021/3/5
@@ -35,70 +37,58 @@ import java.util.Map;
 @Slf4j
 public class YunpianSmsClient extends AbstractSmsClient {
 
-    private final YunpianClient client;
+    /**
+     * 云信短信客户端
+     */
+    private volatile YunpianClient client;
 
     private final TypeReference<List<Map<String, String>>> callbackType = new TypeReference<List<Map<String, String>>>() {
     };
 
-    /**
-     * 构造云片短信发送处理
-     *
-     * @param channelVO 阿里云短信配置
-     */
-    public YunpianSmsClient(SmsChannelProperty channelVO) {
-        super(channelVO);
-        client = new YunpianClient(channelVO.getApiKey());
+    public YunpianSmsClient(SmsChannelProperties properties) {
+        super(properties);
     }
 
     @Override
-    public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) {
-        Map<String, String> paramMap = new HashMap<>();
-        paramMap.put(YunpianConstant.APIKEY, getProperty().getApiKey());
-        paramMap.put(YunpianConstant.MOBILE, String.join(SmsConstants.COMMA, targetPhone));
-        paramMap.put(YunpianConstant.TEXT, formatContent(smsBody));
-        paramMap.put(Helper.CALLBACK, getProperty().getCallbackUrl());
-
-        Result<SmsSingleSend> sendResult = client.sms().single_send(paramMap);
-        boolean success = sendResult.getCode().equals(Code.OK);
-
-        if (!success) {
-            log.debug("send fail[code={}, message={}]", sendResult.getCode(), sendResult.getDetail());
-        }
-        return new SmsResult()
-                .setSuccess(success)
-                .setMessage(sendResult.getDetail())
-                .setCode(sendResult.getCode().toString())
-                .setApiId(sendResult.getData().getSid().toString());
+    public void doInit() {
+        client = new YunpianClient(properties.getApiKey());
     }
 
-
-    /**
-     * 格式化短信内容,将参数注入到模板中
-     *
-     * @param smsBody 短信信息
-     * @return 格式化后的短信内容
-     */
-    private String formatContent(SmsBody smsBody) {
-        StringBuilder result = new StringBuilder(smsBody.getTemplateContent());
-        smsBody.getParams().forEach((key, val) -> {
-            String param = parseParamToPlaceholder(key);
-            result.replace(result.indexOf(param), result.indexOf(param + param.length()), val);
-        });
-        return result.toString();
+    @Override
+    protected SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) throws Throwable {
+        // 构建参数
+        Map<String, String> request = new HashMap<>();
+        request.put(YunpianConstant.APIKEY, properties.getApiKey());
+        request.put(YunpianConstant.MOBILE, mobile);
+        request.put(YunpianConstant.TPL_ID, apiTemplateId);
+        request.put(YunpianConstant.TPL_VALUE, formatTplValue(templateParams));
+        request.put(YunpianConstant.UID, String.valueOf(sendLogId));
+        request.put(Helper.CALLBACK, properties.getCallbackUrl());
+
+        // 执行发送
+        Result<SmsSingleSend> sendResult = client.sms().tpl_single_send(request);
+        if (sendResult.getThrowable() != null) {
+            throw sendResult.getThrowable();
+        }
+        // 解析结果
+        SmsSingleSend data = sendResult.getData();
+        return SmsResult.success(parseSendFailureType(sendResult), // 将 API 短信平台,解析成统一的错误码
+                String.valueOf(data.getCode()), data.getMsg(), null, String.valueOf(data.getSid()));
     }
 
-    /**
-     * 将指定参数改成对应的占位字符
-     * <p>
-     * 云片的是 #param# 的形式作为占位符
-     *
-     * @param key 参数名
-     * @return 对应的占位字符
-     */
-    private String parseParamToPlaceholder(String key) {
-        return SmsConstants.JING_HAO + key + SmsConstants.JING_HAO;
+    private static String formatTplValue(Map<String, Object> templateParams) {
+        if (CollUtil.isEmpty(templateParams)) {
+            return "";
+        }
+        // 参考 https://www.yunpian.com/official/document/sms/zh_cn/introduction_demos_encode_sample 格式化
+        StringJoiner joiner = new StringJoiner("&");
+        templateParams.forEach((key, value) -> joiner.add(String.format("#%s#=%s", key, URLUtil.encode(String.valueOf(value)))));
+        return joiner.toString();
     }
 
+    private static SmsSendFailureTypeEnum parseSendFailureType(Result<SmsSingleSend> sendResult) {
+        return SmsSendFailureTypeEnum.SMS_UNKNOWN;
+    }
 
     /**
      * 云片的比较复杂,又是加密又是套娃的
@@ -109,7 +99,6 @@ public class YunpianSmsClient extends AbstractSmsClient {
         return Helper.getSmsResultDetailByParam(map);
     }
 
-
     /**
      * 从 request 中获取请求中传入的短信发送结果信息
      *
@@ -155,8 +144,8 @@ public class YunpianSmsClient extends AbstractSmsClient {
         private static int getSendStatus(Map<String, String> map) {
             String reportStatus = map.get(REPORT_STATUS);
             return SmsConstants.SUCCESS.equals(reportStatus)
-                    ? SysSmsSendStatusEnum.SEND_SUCCESS.getStatus()
-                    : SysSmsSendStatusEnum.SEND_FAIL.getStatus();
+                    ? SysSmsSendStatusEnum.SUCCESS.getStatus()
+                    : SysSmsSendStatusEnum.FAILURE.getStatus();
         }
 
         public static SmsResultDetail getSmsResultDetailByParam(Map<String, String> map) {

+ 11 - 2
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsSendFailureTypeEnum.java

@@ -13,14 +13,23 @@ import lombok.Getter;
 public enum SmsSendFailureTypeEnum {
 
     // ========== 模板相关(100 开头) ==========
-    SMS_TEMPLATE_DISABLE(100), // 短信模板被禁用
+    SMS_CHANNEL_CLIENT_NOT_EXISTS(100, "短信渠道的客户端不存在"),
 
-    // ========== 其它相关 ==========
+    // ========== 模板相关(200 开头) ==========
+    SMS_TEMPLATE_DISABLE(200, "短信模板被禁用"),
+
+    // ========== 其它相关(900 开头) ==========
+    SMS_SEND_EXCEPTION(900, "发送异常"),
+    SMS_UNKNOWN(999, "未知错误,需要解析")
     ;
 
     /**
      * 失败类型
      */
     private final int type;
+    /**
+     * 失败提示
+     */
+    private final String msg;
 
 }

+ 52 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperties.java

@@ -0,0 +1,52 @@
+package cn.iocoder.dashboard.framework.sms.core.property;
+
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 短信渠道配置类
+ *
+ * @author zzf
+ * @date 2021/1/25 17:01
+ */
+@Data
+@Validated
+public class SmsChannelProperties {
+
+    /**
+     * 渠道编号
+     */
+    @NotNull(message = "短信渠道 ID 不能为空")
+    private Long id;
+    /**
+     * 短信签名
+     */
+    @NotEmpty(message = "短信签名不能为空")
+    private String signature;
+    /**
+     * 渠道编码
+     *
+     * 枚举 {@link SmsChannelEnum}
+     */
+    @NotEmpty(message = "渠道编码不能为空")
+    private String code;
+    /**
+     * 短信 API 的账号
+     */
+    @NotEmpty(message = "短信 API 的账号不能为空")
+    private String apiKey;
+    /**
+     * 短信 API 的秘钥
+     */
+    @NotEmpty(message = "短信 API 的秘钥不能为空")
+    private String apiSecret;
+    /**
+     * 短信发送回调 URL
+     */
+    private String callbackUrl;
+
+}

+ 0 - 68
src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperty.java

@@ -1,68 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.core.property;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 渠道(包含模板)信息VO类
- *
- * @author zzf
- * @date 2021/1/25 17:01
- */
-@Data
-@EqualsAndHashCode
-public class SmsChannelProperty implements Serializable {
-
-    /**
-     * id
-     */
-    @NotNull(message = "短信渠道ID不能为空")
-    private Long id;
-
-    /**
-     * 编码(来自枚举类 阿里、华为、七牛等)
-     */
-    @NotEmpty(message = "短信渠道编码不能为空")
-    private String code;
-
-    /**
-     * 渠道账号id
-     */
-    @NotEmpty(message = "渠道账号id不能为空")
-    private String apiKey;
-
-    /**
-     * 渠道账号秘钥
-     */
-    @NotEmpty(message = "渠道账号秘钥不能为空")
-    private String apiSecret;
-
-    /**
-     * 实际渠道签名唯一标识
-     */
-    @NotEmpty(message = "实际渠道签名唯一标识不能为空")
-    private String apiSignatureId;
-
-    /**
-     * 签名值
-     */
-    @NotEmpty(message = "签名值不能为空")
-    private String signature;
-
-    /**
-     * 是否拥有回调函数(0否 1是)
-     */
-    @NotNull(message = "是否拥有回调函数不能为空")
-    private Integer hadCallback;
-
-    /**
-     * 短信发送回调url
-     */
-    private String callbackUrl;
-
-}

+ 0 - 47
src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsTemplateProperty.java

@@ -1,47 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.core.property;
-
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import javax.validation.constraints.NotEmpty;
-
-/**
- * 渠道模板VO类
- *
- * @author zzf
- * @date 2021/1/25 17:03
- */
-@Data
-@EqualsAndHashCode
-public class SmsTemplateProperty {
-
-    /**
-     * 渠道id
-     */
-    @NotEmpty(message = "短信渠道编码不能为空")
-    private Long channelId;
-
-    /**
-     * 业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)
-     */
-    private String bizCode;
-
-    /**
-     * 编码
-     */
-    @NotEmpty(message = "短信模板编码不能为空")
-    private String code;
-
-    /**
-     * 实际渠道模板唯一标识
-     */
-    @NotEmpty(message = "短信模板唯一标识不能为空")
-    private String apiTemplateId;
-
-    /**
-     * 内容
-     */
-    @NotEmpty(message = "短信模板内容不能为空")
-    private String content;
-
-}

+ 0 - 3
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/req/SmsChannelPageReqVO.java

@@ -15,9 +15,6 @@ import lombok.NoArgsConstructor;
 @EqualsAndHashCode(callSuper = true)
 public class SmsChannelPageReqVO extends PageParam {
 
-    @ApiModelProperty(value = "渠道名", example = "阿里", notes = "模糊匹配")
-    private String name;
-
     @ApiModelProperty(value = "签名值", example = "源码", notes = "模糊匹配")
     private String signature;
 

+ 3 - 10
src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsChannelConvert.java

@@ -1,16 +1,13 @@
 package cn.iocoder.dashboard.modules.system.convert.sms;
 
 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
-import cn.iocoder.dashboard.common.pojo.PageResult;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
 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.controller.user.vo.user.SysUserUpdateReqVO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
@@ -20,9 +17,6 @@ public interface SmsChannelConvert {
 
     SmsChannelConvert INSTANCE = Mappers.getMapper(SmsChannelConvert.class);
 
-    @Mapping(source = "records", target = "list")
-    PageResult<SysSmsChannelDO> convertPage(IPage<SysSmsChannelDO> page);
-
     SysSmsChannelDO convert(SmsChannelCreateReqVO bean);
 
     SysSmsChannelDO convert(SysUserUpdateReqVO bean);
@@ -31,9 +25,8 @@ public interface SmsChannelConvert {
 
     List<SmsChannelAllVO> convert(List<SysSmsChannelDO> bean);
 
-    List<SmsChannelProperty> convertProperty(List<SmsChannelAllVO> list);
-
-    List<SmsChannelProperty> convertProperties(List<SysSmsChannelDO> list);
+    List<SmsChannelProperties> convertProperty(List<SmsChannelAllVO> list);
 
+    List<SmsChannelProperties> convertList(List<SysSmsChannelDO> list);
 
 }

+ 0 - 3
src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SmsTemplateConvert.java

@@ -1,7 +1,6 @@
 package cn.iocoder.dashboard.modules.system.convert.sms;
 
 import cn.iocoder.dashboard.common.pojo.PageResult;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
 import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
@@ -24,6 +23,4 @@ public interface SmsTemplateConvert {
 
     SmsTemplateVO convert(SysSmsTemplateDO bean);
 
-    List<SmsTemplateProperty> convertProperty(List<SysSmsTemplateDO> bean);
-
 }

+ 22 - 34
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsChannelDO.java

@@ -1,70 +1,58 @@
 package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
 
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-
 /**
  * 短信渠道
  *
  * @author zzf
  * @since 2021-01-25
  */
+@TableName(value = "sms_channel", autoResultMap = true)
 @Data
 @EqualsAndHashCode(callSuper = true)
-@TableName(value = "sms_channel", autoResultMap = true)
 public class SysSmsChannelDO extends BaseDO {
 
     /**
-     * 自增编号
+     * 渠道编号
      */
     private Long id;
-
-    /**
-     * 编码(来自枚举类 阿里、华为、七牛等)
-     */
-    private String code;
-
     /**
-     * 短信发送回调url
+     * 短信签名
      */
-    private String callback_url;
-
-    /**
-     * 渠道账号id
-     */
-    private String apiKey;
-
+    private String signature;
     /**
-     * 渠道账号秘钥
+     * 渠道编码
+     *
+     * 枚举 {@link SmsChannelEnum}
      */
-    private String apiSecret;
-
+    private String code;
     /**
-     * 实际渠道签名唯一标识
+     * 启用状态
+     *
+     * 枚举 {@link CommonStatusEnum}
      */
-    private String apiSignatureId;
-
+    private Integer status;
     /**
-     * 名称
+     * 备注
      */
-    private String name;
-
+    private String remark;
     /**
-     * 签名值
+     * 短信 API 的账号
      */
-    private String signature;
-
+    private String apiKey;
     /**
-     * 备注
+     * 短信 API 的秘钥
      */
-    private String remark;
-
+    private String apiSecret;
     /**
-     * 启用状态(0正常 1停用)
+     * 短信发送回调 URL
      */
-    private Integer status;
+    private String callbackUrl;
 
 }

+ 10 - 3
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsSendLogDO.java

@@ -106,15 +106,22 @@ public class SysSmsSendLogDO extends BaseDO {
      */
     private Integer sendStatus;
     /**
+     * 时间发送时间
+     */
+    private Date sendTime;
+    /**
      * 发送失败的类型
      *
-     * 枚举 {@link SmsSendFailureTypeEnum}
+     * 枚举 {@link SmsSendFailureTypeEnum#getType()}
      */
     private Integer sendFailureType;
     /**
-     * 发送成功时间
+     * 发送失败的提示
+     *
+     * 一般情况下,使用 {@link SmsSendFailureTypeEnum#getMsg()}
+     * 异常情况下,通过格式化 Exception 的提示存储
      */
-    private Date sendTime;
+    private String sendFailureMsg;
     /**
      * 短信 API 发送失败的类型
      *

+ 0 - 66
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsSendLogDOX.java

@@ -1,66 +0,0 @@
-package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
-
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.experimental.Accessors;
-
-import java.io.Serializable;
-import java.util.Date;
-
-/**
- * 短信日志
- *
- * @author zzf
- * @since 2021-01-25
- */
-@Data
-@EqualsAndHashCode
-@Accessors(chain = true)
-@TableName(value = "sms_send_log", autoResultMap = true)
-public class SysSmsSendLogDOX implements Serializable {
-
-    /**
-     * 自增编号
-     */
-    private Long id;
-
-    /**
-     * 短信渠道编码(来自枚举类)
-     */
-    private String channelCode;
-
-    /**
-     * 短信渠道id
-     */
-    private Long channelId;
-
-    /**
-     * 模板id
-     */
-    private String templateCode;
-
-    /**
-     * 手机号
-     */
-    private String phone;
-
-    /**
-     * 备注
-     */
-    private String remark;
-
-    /**
-     * 发送状态
-     *
-     * @see SysSmsSendStatusEnum
-     */
-    private Integer sendStatus;
-
-    /**
-     * 发送时间
-     */
-    private Date sendTime;
-
-}

+ 10 - 14
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java

@@ -1,31 +1,27 @@
 package cn.iocoder.dashboard.modules.system.dal.mysql.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.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
 
 @Mapper
-public interface SysSmsChannelMapper extends BaseMapper<SysSmsChannelDO> {
+public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> {
 
-    default IPage<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
-        return selectPage(MyBatisUtils.buildPage(reqVO), new LambdaQueryWrapper<SysSmsChannelDO>()
-                .like(StrUtil.isNotBlank(reqVO.getName()), SysSmsChannelDO::getName, reqVO.getName())
-                .like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getName, reqVO.getSignature())
-        );
+    default PageResult<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapper<SysSmsChannelDO>()
+                .like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getSignature, reqVO.getSignature()));
     }
 
-    default List<SysSmsChannelDO> selectEnabledList() {
+    default List<SysSmsChannelDO> selectListByStatus(Integer status) {
         return selectList(new LambdaQueryWrapper<SysSmsChannelDO>()
-                .eq(SysSmsChannelDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
-                .orderByAsc(SysSmsChannelDO::getId)
-        );
+                .eq(SysSmsChannelDO::getStatus, status)
+                .orderByAsc(SysSmsChannelDO::getId));
     }
+
 }

+ 0 - 34
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsQueryLogMapper.java

@@ -1,34 +0,0 @@
-package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
-
-import cn.iocoder.dashboard.common.enums.DefaultBitFieldEnum;
-import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-@Mapper
-public interface SysSmsQueryLogMapper extends BaseMapper<SysSmsSendLogDO> {
-
-    /**
-     * 查询还没有获取发送结果的短信请求信息
-     */
-    default List<SysSmsSendLogDO> selectNoResultQueryLogList() {
-        return this.selectList(new LambdaQueryWrapper<SysSmsSendLogDO>()
-                .eq(SysSmsSendLogDO::getSendStatus, SysSmsSendStatusEnum.QUERY_SUCCESS)
-                .eq(SysSmsSendLogDO::getGotResult, DefaultBitFieldEnum.NO)
-        );
-    }
-
-
-    /**
-     * 根据APIId修改对象
-     */
-    default boolean updateByApiId(SysSmsSendLogDO queryLogDO, String apiId) {
-        return update(queryLogDO, new LambdaQueryWrapper<SysSmsSendLogDO>()
-                .eq(SysSmsSendLogDO::getApiId, apiId)
-        ) > 0;
-    }
-}

+ 2 - 3
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsSendLogMapper.java

@@ -1,10 +1,9 @@
 package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
 
+import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
-public interface SysSmsSendLogMapper extends BaseMapper<SysSmsSendLogDO> {
-
+public interface SysSmsSendLogMapper extends BaseMapperX<SysSmsSendLogDO> {
 }

+ 0 - 23
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java

@@ -1,17 +1,9 @@
 package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
 
 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
-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.message.sms.SysSmsSendMessage;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
-import cn.iocoder.dashboard.util.json.JsonUtils;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.data.redis.connection.stream.ObjectRecord;
-import org.springframework.data.redis.stream.StreamListener;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
@@ -27,24 +19,9 @@ import javax.annotation.Resource;
 public class SmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
 
     @Resource
-    private SysSmsChannelService smsChannelService;
-
-    @Resource
-    private SysSmsQueryLogService smsQueryLogService;
-
-    @Resource
     private SysSmsService smsService;
 
     @Override
-    public void onMessage(ObjectRecord<String, SmsSendMessage> record) {
-        AbstractSmsClient smsClient = smsChannelService.getSmsClient(body.getTemplateCode());
-        String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(body.getTemplateCode());
-
-        SmsResult result = smsClient.send(templateApiId, body, message.getTargetPhone());
-        smsQueryLogService.afterSendLog(body.getSmsLogId(), result);
-    }
-
-    @Override
     public void onMessage(SysSmsSendMessage message) {
         log.info("[onMessage][消息内容({})]", message);
         smsService.doSendSms(message);

+ 0 - 24
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelService.java

@@ -1,8 +1,6 @@
 package cn.iocoder.dashboard.modules.system.service.sms;
 
 import cn.iocoder.dashboard.common.pojo.PageResult;
-import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
-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;
@@ -46,26 +44,4 @@ public interface SysSmsChannelService {
      */
     List<SmsChannelEnumRespVO> getSmsChannelEnums();
 
-    /**
-     * 根据短信模板编码获取短信客户端
-     *
-     * @param templateCode 短信模板编码
-     * @return 短信客户端
-     */
-    AbstractSmsClient getSmsClient(String templateCode);
-
-    /**
-     * 根据短信模板编码获取模板唯一标识
-     *
-     * @param templateCode 短信模板编码
-     * @return 短信客户端
-     */
-    String getSmsTemplateApiIdByCode(String templateCode);
-
-    /**
-     * 查询渠道(包含名下模块)信息集合
-     *
-     * @return 渠道(包含名下模块)信息集合
-     */
-    List<SmsChannelAllVO> listSmsChannelAllEnabledInfo();
 }

+ 0 - 35
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java

@@ -1,35 +0,0 @@
-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 cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
-
-/**
- * 短信请求日志服务接口
- *
- * @author zzf
- * @date 2021/1/25 9:24
- */
-public interface SysSmsQueryLogService {
-
-    /**
-     * 发送短信前的日志处理
-     *
-     * @param smsBody     短信内容
-     * @param targetPhone 发送对象手机号
-     * @param client      短信客户端
-     * @return 生成的日志id
-     */
-    void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client);
-
-    /**
-     * 发送消息后的日志处理
-     *
-     * @param logId  日志id
-     * @param result 消息结果
-     */
-    void afterSendLog(Long logId, SmsResult result);
-
-    void updateSendLogByResultDetail(SmsResultDetail smsResultDetail);
-}

+ 27 - 5
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsSendLogService.java

@@ -1,5 +1,6 @@
 package cn.iocoder.dashboard.modules.system.service.sms;
 
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
 
 import java.util.Map;
@@ -12,17 +13,38 @@ import java.util.Map;
  */
 public interface SysSmsSendLogService {
 
+    /**
+     * 创建发送日志
+     *
+     * @param mobile 手机号
+     * @param userId 用户编号
+     * @param userType 用户类型
+     * @param template 短信模板
+     * @param templateContent 短信内容
+     * @param templateParams 短信参数
+     * @return
+     */
     Long createSmsSendLog(String mobile, Long userId, Integer userType,
                           SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams);
 
     /**
-     * 更新发送日志为失败
+     * 更新发送日志的结果
      *
-     * @param id 发送日志编号
-     * @param sendFailureType 失败类型
+     * @param id 日志编号
+     * @param success 是否成功
+     * @param sendFailureType 发送失败的类型
+     * @param sendFailureMsg 发送失败的提示
+     * @param apiSendFailureType 短信 API 发送失败的类型
+     * @param apiSendFailureMsg 短信 API 发送失败的提示
+     * @param apiRequestId 短信 API 发送返回的唯一请求 ID
+     * @param apiSerialNo 短信 API 发送返回的序号
      */
-    void updateSmsSendLogFailure(Long id, Integer sendFailureType);
+    void updateSmsSendLogResult(Long id, Boolean success, Integer sendFailureType, String sendFailureMsg,
+                                String apiSendFailureType, String apiSendFailureMsg, String apiRequestId, String apiSerialNo);
 
-    void getAndSaveSmsSendLog();
+    default void updateSmsSendLogFailure(Long id, SmsSendFailureTypeEnum sendFailureType) {
+        updateSmsSendLogResult(id, false, sendFailureType.getType(), sendFailureType.getMsg(),
+                null, null, null, null);
+    }
 
 }

+ 28 - 59
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java

@@ -1,32 +1,24 @@
 package cn.iocoder.dashboard.modules.system.service.sms.impl;
 
-import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.common.pojo.PageResult;
-import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
-import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
-import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
 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.SysSmsChannelMapper;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsTemplateMapper;
 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.SysSmsChannelMapper;
+import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
 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实现类
@@ -37,10 +29,8 @@ import java.util.concurrent.ConcurrentHashMap;
 @Service
 public class SysSmsChannelServiceImpl implements SysSmsChannelService {
 
-    private final Map<String, Long> templateCode2ChannelIdMap = new ConcurrentHashMap<>(32);
-
     @Resource
-    private SmsClientFactory clientFactory;
+    private SmsClientFactory smsClientFactory;
 
     @Resource
     private SysSmsChannelMapper channelMapper;
@@ -48,30 +38,19 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
     @Resource
     private SysSmsTemplateMapper templateMapper;
 
-
-    @PostConstruct
     @Override
+    @PostConstruct
     public void initSmsClientAndCacheSmsTemplate() {
         // 查询有效渠道信息
-        List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
-        List<SmsChannelProperty> propertyList = SmsChannelConvert.INSTANCE.convertProperties(channelDOList);
-
-        // 遍历渠道生成client、获取模板并缓存
-        propertyList.forEach(channelProperty -> {
-            List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(channelProperty.getId());
-            if (ObjectUtil.isNotEmpty(templateDOList)) {
-                Long clientId = clientFactory.createClient(channelProperty);
-                templateDOList.forEach(template -> templateCode2ChannelIdMap.put(template.getCode(), clientId));
-
-                List<SmsTemplateProperty> templatePropertyList = SmsTemplateConvert.INSTANCE.convertProperty(templateDOList);
-                clientFactory.addOrUpdateTemplateCache(templatePropertyList);
-            }
-        });
+        List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        // 创建渠道 Client
+        List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList(channelDOList);
+        propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
     }
 
     @Override
     public PageResult<SysSmsChannelDO> pageSmsChannels(SmsChannelPageReqVO reqVO) {
-        return SmsChannelConvert.INSTANCE.convertPage(channelMapper.selectChannelPage(reqVO));
+        return channelMapper.selectChannelPage(reqVO);
     }
 
     @Override
@@ -86,30 +65,20 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
         return SmsChannelConvert.INSTANCE.convertEnum(Arrays.asList(SmsChannelEnum.values()));
     }
 
-    @Override
-    public AbstractSmsClient getSmsClient(String templateCode) {
-        return clientFactory.getClient(templateCode2ChannelIdMap.get(templateCode));
-    }
-
-    @Override
-    public String getSmsTemplateApiIdByCode(String templateCode) {
-        return clientFactory.getTemplateApiIdByCode(templateCode);
-    }
-
-    @Override
-    public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
-        List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
-        if (ObjectUtil.isNull(channelDOList)) {
-            return null;
-        }
-        List<SmsChannelAllVO> channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList);
-        channelAllVOList.forEach(smsChannelDO -> {
-            List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(smsChannelDO.getId());
-            if (ObjectUtil.isNull(templateDOList)) {
-                templateDOList = new ArrayList<>();
-            }
-            smsChannelDO.setTemplateList(SmsTemplateConvert.INSTANCE.convert(templateDOList));
-        });
-        return channelAllVOList;
-    }
+//    @Override
+//    public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
+//        List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus();
+//        if (ObjectUtil.isNull(channelDOList)) {
+//            return null;
+//        }
+//        List<SmsChannelAllVO> channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList);
+//        channelAllVOList.forEach(smsChannelDO -> {
+//            List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(smsChannelDO.getId());
+//            if (ObjectUtil.isNull(templateDOList)) {
+//                templateDOList = new ArrayList<>();
+//            }
+//            smsChannelDO.setTemplateList(SmsTemplateConvert.INSTANCE.convert(templateDOList));
+//        });
+//        return channelAllVOList;
+//    }
 }

+ 0 - 63
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsQueryLogServiceImpl.java

@@ -1,63 +0,0 @@
-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.framework.sms.core.SmsResultDetail;
-import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
-import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-
-/**
- * 短信请求日志服务实现类
- *
- * @author zzf
- * @date 13:50 2021/3/2
- */
-@Service
-public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
-
-    @Resource
-    private SysSmsQueryLogMapper logMapper;
-
-    @Override
-    public void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client) {
-        SysSmsSendLogDO smsLog = new SysSmsSendLogDO();
-        SmsChannelProperty property = client.getProperty();
-
-        smsLog.setChannelCode(property.getCode())
-                .setChannelId(property.getId())
-                .setTemplateCode(smsBody.getTemplateCode())
-                .setPhone(targetPhone)
-                .setContent(smsBody.getParams().toString());
-
-        smsLog.setSendStatus(SysSmsSendStatusEnum.ASYNC.getStatus());
-        logMapper.insert(smsLog);
-        smsBody.setSmsLogId(smsLog.getId());
-    }
-
-    @Override
-    public void afterSendLog(Long logId, SmsResult result) {
-        SysSmsSendLogDO smsLog = new SysSmsSendLogDO();
-        smsLog.setId(logId);
-        smsLog.setApiId(result.getApiId());
-        smsLog.setSendStatus(SysSmsSendStatusEnum.QUERY_FAIL.getStatus());
-        smsLog.setRemark(result.getCode() + ": " + result.getMessage());
-        logMapper.updateById(smsLog);
-    }
-
-    @Override
-    public void updateSendLogByResultDetail(SmsResultDetail smsResultDetail) {
-        SysSmsSendLogDO queryLogDO = new SysSmsSendLogDO();
-        queryLogDO.setSendStatus(smsResultDetail.getSendStatus());
-        queryLogDO.setSendTime(smsResultDetail.getSendTime());
-        queryLogDO.setRemark(smsResultDetail.getMessage());
-        logMapper.updateByApiId(queryLogDO, smsResultDetail.getApiId());
-    }
-
-}

+ 8 - 83
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsSendLogServiceImpl.java

@@ -1,22 +1,17 @@
 package cn.iocoder.dashboard.modules.system.service.sms.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
-import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
-import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDOX;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
-import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsQueryLogMapper;
 import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsSendLogMapper;
 import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.List;
+import java.util.Date;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * 短信发送日志服务实现类
@@ -29,19 +24,8 @@ import java.util.Map;
 public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
 
     @Resource
-    private SysSmsQueryLogMapper smsQueryLogMapper;
-
-    @Resource
     private SysSmsSendLogMapper smsSendLogMapper;
 
-    @Resource
-    private SysSmsChannelService smsChannelService;
-
-    /**
-     * 定时执行 {@link #getSmsSendResultJob()} 的周期
-     */
-    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
     @Override
     public Long createSmsSendLog(String mobile, Long userId, Integer userType,
                                  SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
@@ -61,71 +45,12 @@ public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
     }
 
     @Override
-    public void updateSmsSendLogFailure(Long id, Integer sendFailureType) {
-        smsSendLogMapper.updateById(new SysSmsSendLogDO().setId(id).setSendFailureType(sendFailureType));
-    }
-
-    @Override
-    public void getAndSaveSmsSendLog() {
-
-        List<SysSmsSendLogDO> noResultQueryLogList = smsQueryLogMapper.selectNoResultQueryLogList();
-
-        if (CollectionUtil.isEmpty(noResultQueryLogList)) {
-            return;
-        }
-        //用于添加的发送日志对象
-        SysSmsSendLogDOX insertSendLog = new SysSmsSendLogDOX();
-        //用于修改状态的请求日志对象
-        SysSmsSendLogDO updateQueryLog = new SysSmsSendLogDO();
-
-        noResultQueryLogList.forEach(queryLog -> {
-            AbstractSmsClient smsClient = smsChannelService.getSmsClient(queryLog.getTemplateCode());
-
-            updateQueryLog.setId(queryLog.getId());
-
-            // 只处理实现了获取发送结果方法的短信客户端,理论上这里都是满足条件的,以防万一加个判断。
-            /*if (smsClient instanceof NeedQuerySendResultSmsClient) {
-                //初始化点字段值
-                queryLog2SendLong(insertSendLog, queryLog);
-
-                NeedQuerySendResultSmsClient querySendResultSmsClient = (NeedQuerySendResultSmsClient) smsClient;
-                try {
-                    List<SmsResultDetail> smsSendResult = querySendResultSmsClient.getSmsSendResult(queryLog.getRemark());
-                    smsSendResult.forEach(resultDetail -> {
-                        insertSendLog.setPhone(resultDetail.getPhone());
-                        insertSendLog.setSendStatus(resultDetail.getSendStatus());
-                        insertSendLog.setSendTime(resultDetail.getSendTime());
-                        insertSendLog.setRemark(resultDetail.getMessage());
-                        smsSendLogMapper.insert(insertSendLog);
-                    });
-                } catch (Exception e) {
-                    //exception handle
-                    log.error("query send result fail, exception: " + e.getMessage());
-
-                    updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
-                    updateQueryLog.setRemark(e.getMessage());
-                    smsQueryLogMapper.updateById(updateQueryLog);
-                    return;
-                }
-            } else {
-                //理论上这里都是满足条件的,以防万一加个判断。
-                updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
-                smsQueryLogMapper.updateById(updateQueryLog);
-            }*/
-            updateQueryLog.setSendStatus(SysSmsSendStatusEnum.SEND_SUCCESS.getStatus());
-            updateQueryLog.setRemark(String.format("日志(id = %s)对应的客户端没有继承NeedQuerySendResultSmsClient, 不能获取短信结果。", queryLog.getId()));
-            smsQueryLogMapper.updateById(updateQueryLog);
-        });
-    }
-
-    private void queryLog2SendLong(SysSmsSendLogDOX insertSendLog, SysSmsSendLogDO queryLog) {
-        insertSendLog.setChannelCode(queryLog.getChannelCode());
-        insertSendLog.setChannelId(queryLog.getChannelId());
-        insertSendLog.setTemplateCode(queryLog.getTemplateCode());
+    public void updateSmsSendLogResult(Long id, Boolean success, Integer sendFailureType, String sendFailureMsg,
+                                       String apiSendFailureType, String apiSendFailureMsg, String apiRequestId, String apiSerialNo) {
+        SysSmsSendStatusEnum sendStatus = Objects.equals(success, true) ? SysSmsSendStatusEnum.SUCCESS : SysSmsSendStatusEnum.FAILURE;
+        smsSendLogMapper.updateById(new SysSmsSendLogDO().setId(id).setSendStatus(sendStatus.getStatus()).setSendTime(new Date())
+                .setSendFailureType(sendFailureType).setSendFailureMsg(sendFailureMsg)
+                .setApiSendFailureType(apiSendFailureType).setApiSendFailureMsg(apiSendFailureMsg).setApiRequestId(apiRequestId).setApiSerialNo(apiSerialNo));
     }
 
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void getSmsSendResultJob() {
-        getAndSaveSmsSendLog();
-    }
 }

+ 27 - 19
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java

@@ -4,15 +4,19 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.common.enums.UserTypeEnum;
-import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
-import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
+import cn.iocoder.dashboard.framework.sms.core.SmsResult;
+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.enums.SmsSendFailureTypeEnum;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
 import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
-import cn.iocoder.dashboard.modules.system.service.sms.*;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
 import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -31,29 +35,21 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  * @date 2021/1/25 9:25
  */
 @Service
+@Slf4j
 public class SysSmsServiceImpl implements SysSmsService {
 
     @Resource
     private SysSmsTemplateService smsTemplateService;
     @Resource
     private SysSmsSendLogService smsSendLogService;
-
-    @Resource
-    private SysUserService userService;
-
-    @Resource
-    private SysSmsChannelService channelService;
-
-
-    @Resource
-    private SysSmsQueryLogService logService;
-
     @Resource
     private SysSmsProducer smsProducer;
-
     @Resource
     private SmsClientFactory smsClientFactory;
 
+    @Resource
+    private SysUserService userService;
+
     @Override
     public void sendSingleSms(String mobile, Long userId, Integer userType,
                               String templateCode, Map<String, Object> templateParams) {
@@ -68,7 +64,7 @@ public class SysSmsServiceImpl implements SysSmsService {
 
         // 如果模板被禁用,则直接标记发送失败。也就说,不发短信,嘿嘿。
         if (CommonStatusEnum.DISABLE.getStatus().equals(template.getStatus())) {
-            smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE.getType());
+            smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE);
             return;
         }
         // 如果模板未禁用,发送 MQ 消息。目的是,异步化调用短信平台
@@ -126,19 +122,31 @@ public class SysSmsServiceImpl implements SysSmsService {
             SysUserDO user = userService.getUser(userId);
             return user != null ? user.getMobile() : null;
         }
+        // TODO 芋艿:支持 C 端用户
         return null;
     }
 
     @Override
     public void doSendSms(SysSmsSendMessage message) {
+        // 获得渠道对应的 SmsClient 客户端
+        SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
+        if (smsClient == null) {
+            log.error("[doSendSms][短信 message({}) 找不到对应的客户端]", message);
+            smsSendLogService.updateSmsSendLogFailure(message.getSendLogId(), SmsSendFailureTypeEnum.SMS_CHANNEL_CLIENT_NOT_EXISTS);
+            return;
+        }
 
+        // 发送短信
+        SmsResult sendResult = smsClient.send(message.getSendLogId(), message.getMobile(),
+                message.getApiTemplateId(), message.getTemplateParams());
     }
 
     @Override
     public Object smsSendCallbackHandle(ServletRequest request) {
-        SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
-        logService.updateSendLogByResultDetail(smsResultDetail);
-        return smsResultDetail.getCallbackResponseBody();
+//        SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
+//        logService.updateSendLogByResultDetail(smsResultDetail);
+//        return smsResultDetail.getCallbackResponseBody();
+        return null;
     }
 
 }