zengzefeng преди 4 години
родител
ревизия
767cd90279
променени са 30 файла, в които са добавени 735 реда и са изтрити 230 реда
  1. 9 7
      pom.xml
  2. 34 8
      sql/sms.sql
  3. 27 0
      src/main/java/cn/iocoder/dashboard/common/enums/DefaultBitFieldEnum.java
  4. 1 1
      src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java
  5. 22 15
      src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java
  6. 25 0
      src/main/java/cn/iocoder/dashboard/framework/sms/client/HadCallbackSmsClient.java
  7. 24 0
      src/main/java/cn/iocoder/dashboard/framework/sms/client/NeedQuerySendResultSmsClient.java
  8. 5 0
      src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java
  9. 132 0
      src/main/java/cn/iocoder/dashboard/framework/sms/client/YunpianSmsClient.java
  10. 5 1
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java
  11. 18 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsConstants.java
  12. 9 4
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java
  13. 2 2
      src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResultDetail.java
  14. 11 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperty.java
  15. 27 0
      src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java
  16. 27 0
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsQueryLogMapper.java
  17. 2 2
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsSendLogMapper.java
  18. 10 0
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsChannelDO.java
  19. 94 0
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsQueryLogDO.java
  20. 10 17
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsSendLogDO.java
  21. 15 9
      src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java
  22. 14 4
      src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java
  23. 3 4
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java
  24. 13 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsSendLogService.java
  25. 11 58
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java
  26. 0 77
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsLogServiceImpl.java
  27. 59 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsQueryLogServiceImpl.java
  28. 109 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsSendLogServiceImpl.java
  29. 8 20
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
  30. 9 1
      src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java

+ 9 - 7
pom.xml

@@ -183,12 +183,6 @@
             <version>${jjwt.version}</version>
         </dependency>
 
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-            <version>${lombok.version}</version>
-        </dependency>
-
         <dependency>
             <groupId>org.mapstruct</groupId>
             <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
@@ -222,17 +216,25 @@
             <version>${easyexcel.verion}</version>
         </dependency>
 
+
+        <!-- SMS SDK begin -->
+        <dependency>
+            <groupId>com.yunpian.sdk</groupId>
+            <artifactId>yunpian-java-sdk</artifactId>
+            <version>1.2.7</version>
+        </dependency>
+
         <dependency>
             <groupId>com.aliyun</groupId>
             <artifactId>aliyun-java-sdk-core</artifactId>
             <version>4.5.18</version>
         </dependency>
-
         <dependency>
             <groupId>com.aliyun</groupId>
             <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
             <version>2.1.0</version>
         </dependency>
+        <!-- SMS SDK end -->
 
     </dependencies>
 

+ 34 - 8
sql/sms.sql

@@ -12,6 +12,8 @@ CREATE TABLE `sms_channel`
     `code`             varchar(50)  NOT NULL COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
     `api_key`          varchar(100) NOT NULL COMMENT '账号id',
     `api_secret`       varchar(100) NOT NULL COMMENT '账号秘钥',
+    `had_callback`     bit(1)       NOT NULL DEFAULT b'0' COMMENT '是否拥有回调函数',
+    `callback_url`     varchar(100) NOT NULL default '' COMMENT '回调请求路径',
     `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
     `name`             varchar(50)  NOT NULL COMMENT '名称',
     `signature`        varchar(50)  NOT NULL COMMENT '签名值',
@@ -60,23 +62,47 @@ CREATE TABLE `sms_template`
   AUTO_INCREMENT = 1
   DEFAULT CHARSET = utf8mb4 COMMENT ='短信模板';
 
+-- ----------------------------
+-- Table structure for sms_query_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_query_log`;
+CREATE TABLE `sms_query_log`
+(
+    `id`                bigint(20)    NOT NULL AUTO_INCREMENT COMMENT '自增编号',
+    `api_id`            varchar(100)  NOT NULL COMMENT '第三方唯一标识',
+    `channel_code`      varchar(50)   NOT NULL COMMENT '短信渠道编码(来自枚举类)',
+    `channel_id`        bigint(20)    NOT NULL COMMENT '短信渠道id',
+    `template_code`     varchar(50)   NOT NULL COMMENT '渠道编码',
+    `phones`            varchar(2000) NOT NULL COMMENT '手机号(数组json字符串)',
+    `content`           varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
+    `send_result_param` varchar(200)  NOT NULL DEFAULT '' COMMENT '查询短信发送结果的参数',
+    `send_status`       tinyint(1)    NOT NULL DEFAULT 2 COMMENT '发送状态(0本地异步中 1发送请求失败 2发送请求成功)',
+    `got_result`        bit(1)        NOT NULL DEFAULT b'0' COMMENT '是否获取发送结果',
+    `had_callback`      bit(1)        NOT NULL DEFAULT b'0' COMMENT '是否拥有回调函数',
+    `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 ='短信请求日志';
+
 -- ----------------------------
 -- Table structure for sms_log
 -- ----------------------------
-DROP TABLE IF EXISTS `sms_log`;
-CREATE TABLE `sms_log`
+DROP TABLE IF EXISTS `sms_send_log`;
+CREATE TABLE `sms_send_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字符串)',
+    `query_log_id`  bigint(20)    NOT NULL COMMENT '请求日志id',
+    `phone`         char(11)      NOT NULL COMMENT '手机号',
     `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 '创建时间',
+    `remark`        varchar(200)           DEFAULT NULL COMMENT '备注',
+    `success`       tinyint(1)    NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `send_time`     datetime               DEFAULT NULL COMMENT '创建时间',
     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB
   AUTO_INCREMENT = 1
-  DEFAULT CHARSET = utf8mb4 COMMENT ='短信日志';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='短信发送日志';

+ 27 - 0
src/main/java/cn/iocoder/dashboard/common/enums/DefaultBitFieldEnum.java

@@ -0,0 +1,27 @@
+package cn.iocoder.dashboard.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 通用状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum DefaultBitFieldEnum {
+
+    NO(0, "否"),
+    YES(1, "是");
+
+    /**
+     * 状态值
+     */
+    private final Integer val;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+}

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

@@ -58,7 +58,7 @@ public abstract class AbstractSmsClient implements SmsClient {
      * @return 短信发送结果
      * @throws Exception 调用发送失败,抛出异常
      */
-    public abstract SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception;
+    protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception;
 
     protected void beforeSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception {
     }

+ 22 - 15
src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java

@@ -2,7 +2,6 @@ package cn.iocoder.dashboard.framework.sms.client;
 
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
@@ -14,11 +13,11 @@ import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
 import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
+import com.aliyuncs.exceptions.ClientException;
 import com.aliyuncs.http.MethodType;
 import com.aliyuncs.profile.DefaultProfile;
 import com.aliyuncs.profile.IClientProfile;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -31,7 +30,7 @@ import java.util.List;
  * @date 2021/1/25 14:17
  */
 @Slf4j
-public class AliyunSmsClient extends AbstractSmsClient {
+public class AliyunSmsClient extends AbstractSmsClient implements NeedQuerySendResultSmsClient {
 
     private static final String OK = "OK";
 
@@ -70,35 +69,43 @@ public class AliyunSmsClient extends AbstractSmsClient {
         request.setTemplateParam(smsBody.getParamsStr());
         SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
 
-        boolean result = OK.equals(sendSmsResponse.getCode());
-        if (!result) {
+        boolean success = OK.equals(sendSmsResponse.getCode());
+        if (!success) {
             log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
         }
-        SmsResult resultBody = new SmsResult();
-        resultBody.setSuccess(result);
+        return new SmsResult()
+                .setSuccess(success)
+                .setMessage(sendSmsResponse.getMessage())
+                .setCode(sendSmsResponse.getCode())
+                .setApiId(sendSmsResponse.getBizId())
+                .setSendResultParam(sendSmsResponse.getBizId());
+    }
+
+
+    @Override
+    public List<SmsResultDetail> getSmsSendResult(String param) throws ClientException {
         QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
-        querySendDetailsRequest.setBizId(sendSmsResponse.getBizId());
-        // TODO FROM 芋艿 to zzf:发送完之后,基于短信平台回调,去更新回执状态。短信发送是否成功,和最终用户收到,是两个维度。这块有困惑,可以微信,我给个截图哈。
+        querySendDetailsRequest.setBizId(param);
+        // TODO FROM 芋艿 to zzf:发送完之后,基于短信平台回调,去更新回执状态。短信发送是否成功,和最终用户收到,是两个维度。这块有困惑,可以微信,我给个截图哈。 DONE
         QuerySendDetailsResponse acsResponse = acsClient.getAcsResponse(querySendDetailsRequest);
         List<SmsResultDetail> resultDetailList = new ArrayList<>(Integer.parseInt(acsResponse.getTotalCount()));
         acsResponse.getSmsSendDetailDTOs().forEach(s -> {
             SmsResultDetail resultDetail = new SmsResultDetail();
-            resultDetail.setCreateTime(DateUtil.parseDateTime(s.getSendDate()));
+            resultDetail.setSendTime(DateUtil.parseDateTime(s.getSendDate()));
             resultDetail.setMessage(s.getContent());
             resultDetail.setPhone(s.getPhoneNum());
-            resultDetail.setStatus(statusConvert(s.getSendStatus()));
+            resultDetail.setSendStatus(statusConvert(s.getSendStatus()));
             resultDetailList.add(resultDetail);
         });
-        resultBody.setResult(resultDetailList);
-        return resultBody;
+        return resultDetailList;
     }
 
     private int statusConvert(Long aliSendStatus) {
         if (aliSendStatus == 1L) {
-            return SmsSendStatusEnum.SUCCESS.getStatus();
+            return SmsSendStatusEnum.SEND_SUCCESS.getStatus();
         }
         if (aliSendStatus == 2L) {
-            return SmsSendStatusEnum.FAIL.getStatus();
+            return SmsSendStatusEnum.SEND_FAIL.getStatus();
         }
         return SmsSendStatusEnum.WAITING.getStatus();
     }

+ 25 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/client/HadCallbackSmsClient.java

@@ -0,0 +1,25 @@
+package cn.iocoder.dashboard.framework.sms.client;
+
+import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
+
+import javax.servlet.ServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+/**
+ * 需要发送请求获取短信发送结果的短信客户端
+ *
+ * @author zzf
+ * @date 2021/3/4 17:20
+ */
+public interface HadCallbackSmsClient {
+
+    /**
+     * 获取短信发送结果
+     *
+     * @param request 请求
+     * @return 短信发送结果
+     */
+    List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws Exception;
+
+}

+ 24 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/client/NeedQuerySendResultSmsClient.java

@@ -0,0 +1,24 @@
+package cn.iocoder.dashboard.framework.sms.client;
+
+import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
+import com.aliyuncs.exceptions.ClientException;
+
+import java.util.List;
+
+/**
+ * 需要发送请求获取短信发送结果的短信客户端
+ *
+ * @author zzf
+ * @date 2021/3/4 17:20
+ */
+public interface NeedQuerySendResultSmsClient {
+
+    /**
+     * 获取短信发送结果
+     *
+     * @param param 参数
+     * @return 短信发送结果
+     */
+    List<SmsResultDetail> getSmsSendResult(String param) throws Exception;
+
+}

+ 5 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java

@@ -2,8 +2,10 @@ 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.SmsResultDetail;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * 短信父接口
@@ -23,4 +25,7 @@ public interface SmsClient {
      */
     SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets);
 
+
+    //List<SmsResultDetail> getSmsSendResult(String jsonObjectParam);
+
 }

+ 132 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/client/YunpianSmsClient.java

@@ -0,0 +1,132 @@
+package cn.iocoder.dashboard.framework.sms.client;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.iocoder.dashboard.framework.sms.core.SmsBody;
+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.modules.system.enums.sms.SmsSendStatusEnum;
+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.SmsBatchSend;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.ServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.*;
+
+/**
+ * 云片短信实现类
+ *
+ * @author zzf
+ * @date 9:48 2021/3/5
+ */
+@Slf4j
+public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSmsClient {
+
+    private final 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());
+    }
+
+    @Override
+    public SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) {
+        Map<String, String> paramMap = new HashMap<>();
+        paramMap.put("apikey", getProperty().getApiKey());
+        paramMap.put("mobile", String.join(SmsConstants.COMMA, targets));
+        paramMap.put("text", formatContent(smsBody));
+        paramMap.put("callback", getProperty().getCallbackUrl());
+
+        Result<SmsBatchSend> sendResult = client.sms().batch_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().getData().get(0).getSid().toString());
+    }
+
+
+    /**
+     * 格式化短信内容,将参数注入到模板中
+     *
+     * @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();
+    }
+
+    /**
+     * 将指定参数改成对应的占位字符
+     * <p>
+     * 云片的是 #param# 的形式作为占位符
+     *
+     * @param key 参数名
+     * @return 对应的占位字符
+     */
+    private String parseParamToPlaceholder(String key) {
+        return SmsConstants.JING_HAO + key + SmsConstants.JING_HAO;
+    }
+
+
+    @Override
+    public List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws UnsupportedEncodingException {
+        List<Map<String, String>> stringStringMap = getSendResult(request);
+        List<SmsResultDetail> resultDetailList = new ArrayList<>(stringStringMap.size());
+        stringStringMap.forEach(map -> {
+            SmsResultDetail detail = new SmsResultDetail();
+
+            detail.setPhone(map.get("mobile"));
+            detail.setMessage(map.get("error_msg"));
+            detail.setSendTime(DateUtil.parseTime(map.get("user_receive_time")));
+            String reportStatus = map.get("report_status");
+            detail.setSendStatus(reportStatus.equals(SmsConstants.SUCCESS)
+                    ? SmsSendStatusEnum.SEND_SUCCESS.getStatus()
+                    : SmsSendStatusEnum.SEND_FAIL.getStatus()
+            );
+            resultDetailList.add(detail);
+        });
+        return resultDetailList;
+    }
+
+    /**
+     * 从 request 中获取请求中传入的短信发送结果信息
+     *
+     * @param request 回调请求
+     * @return 短信发送结果信息
+     * @throws UnsupportedEncodingException 解码异常
+     */
+    private List<Map<String, String>> getSendResult(ServletRequest request) throws UnsupportedEncodingException {
+        Map<String, String[]> parameterMap = request.getParameterMap();
+        String[] smsStatuses = parameterMap.get(YunpianConstant.SMS_STATUS);
+        String encode = URLEncoder.encode(smsStatuses[0], CharsetUtil.UTF_8);
+        return JsonUtils.parseByType(encode, callbackType);
+    }
+}

+ 5 - 1
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java

@@ -4,7 +4,6 @@ import cn.iocoder.dashboard.util.json.JsonUtils;
 import lombok.Data;
 
 import java.util.Map;
-import java.util.UUID;
 
 /**
  * 消息内容实体类
@@ -22,6 +21,11 @@ public class SmsBody {
      */
     private String templateCode;
 
+    /**
+     * 模板编码
+     */
+    private String templateContent;
+
     /**
      * 参数列表
      */

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

@@ -0,0 +1,18 @@
+package cn.iocoder.dashboard.framework.sms.core;
+
+/**
+ * 短信相关常量类
+ *
+ * @author zzf
+ * @date 2021/3/5 10:42
+ */
+public interface SmsConstants {
+
+    String OK = "OK";
+
+    String JING_HAO = "#";
+
+    String COMMA = ",";
+
+    String SUCCESS = "SUCCESS";
+}

+ 9 - 4
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java

@@ -1,14 +1,15 @@
 package cn.iocoder.dashboard.framework.sms.core;
 
 import lombok.Data;
+import lombok.experimental.Accessors;
 
 import java.io.Serializable;
-import java.util.List;
 
 /**
  * 消息内容实体类
  */
 @Data
+@Accessors(chain = true)
 public class SmsResult implements Serializable {
 
     /**
@@ -16,6 +17,11 @@ public class SmsResult implements Serializable {
      */
     private Boolean success;
 
+    /**
+     * 第三方唯一标识
+     */
+    private String apiId;
+
     /**
      * 状态码
      */
@@ -27,10 +33,9 @@ public class SmsResult implements Serializable {
     private String message;
 
     /**
-     * 返回值
+     * 用于查询发送结果的参数
      */
-    private List<SmsResultDetail> result;
-
+    private String sendResultParam;
 
     public static SmsResult failResult(String message) {
         SmsResult resultBody = new SmsResult();

+ 2 - 2
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResultDetail.java

@@ -14,7 +14,7 @@ public class SmsResultDetail implements Serializable {
     /**
      * 短信发送状态 {@link cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum}
      */
-    private Integer status;
+    private Integer sendStatus;
 
     /**
      * 接收手机号
@@ -29,5 +29,5 @@ public class SmsResultDetail implements Serializable {
     /**
      * 时间
      */
-    private Date createTime;
+    private Date sendTime;
 }

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

@@ -54,4 +54,15 @@ public class SmsChannelProperty implements Serializable {
     @NotEmpty(message = "签名值不能为空")
     private String signature;
 
+    /**
+     * 是否拥有回调函数(0否 1是)
+     */
+    @NotNull(message = "是否拥有回调函数不能为空")
+    private Integer hadCallback;
+
+    /**
+     * 短信发送回调url
+     */
+    private String callbackUrl;
+
 }

+ 27 - 0
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java

@@ -0,0 +1,27 @@
+package cn.iocoder.dashboard.modules.system.controller.sms;
+
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletRequest;
+
+/**
+ * 短信默认回调接口
+ *
+ * @author zzf
+ * @date 2021/3/5 8:59
+ */
+@RestController("/sms/callback")
+public class SmsDefaultCallbackController {
+
+    @Resource
+    private SysSmsService smsService;
+
+    @RequestMapping("/sms-send")
+    public Object sendSmsCallback(ServletRequest request){
+        return smsService.smsSendCallbackHandle(request);
+    }
+
+}

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

@@ -0,0 +1,27 @@
+package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
+
+import cn.iocoder.dashboard.common.enums.DefaultBitFieldEnum;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
+import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
+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<SysSmsQueryLogDO> {
+
+    /**
+     * 查询还没有获取发送结果的短信请求信息
+     *
+     * @return
+     */
+    default List<SysSmsQueryLogDO> selectNoResultQueryLogList() {
+        return this.selectList(new LambdaQueryWrapper<SysSmsQueryLogDO>()
+                .eq(SysSmsQueryLogDO::getSendStatus, SmsSendStatusEnum.QUERY_SUCCESS)
+                .eq(SysSmsQueryLogDO::getGotResult, DefaultBitFieldEnum.NO)
+                .eq(SysSmsQueryLogDO::getHadCallback, DefaultBitFieldEnum.NO)
+        );
+    }
+}

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsLogMapper.java → src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsSendLogMapper.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.SysSmsLogDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsSendLogDO;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
-public interface SysSmsLogMapper extends BaseMapper<SysSmsLogDO> {
+public interface SysSmsSendLogMapper extends BaseMapper<SysSmsSendLogDO> {
 
 }

+ 10 - 0
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsChannelDO.java

@@ -27,6 +27,16 @@ public class SysSmsChannelDO extends BaseDO {
      */
     private String code;
 
+    /**
+     * 是否拥有回答(0否 1是)
+     */
+    private Integer had_callback;
+
+    /**
+     * 短信发送回调url
+     */
+    private String callback_url;
+
     /**
      * 渠道账号id
      */

+ 94 - 0
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsQueryLogDO.java

@@ -0,0 +1,94 @@
+package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 短信日志
+ *
+ * @author zzf
+ * @since 2021-01-25
+ */
+@Data
+@EqualsAndHashCode
+@Accessors(chain = true)
+@TableName(value = "sms_query_log", autoResultMap = true)
+public class SysSmsQueryLogDO implements Serializable {
+
+    /**
+     * 自增编号
+     */
+    private Long id;
+
+    /**
+     * 短信渠道编码(来自枚举类)
+     */
+    private String channelCode;
+
+    /**
+     * 短信渠道id
+     */
+    private Long channelId;
+
+    /**
+     * 模板id
+     */
+    private String templateCode;
+
+    /**
+     * 手机号
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> phones;
+
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 发送状态
+     *
+     * @see cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum
+     */
+    private Integer sendStatus;
+
+    /**
+     * 是否获取过结果[0否 1是]
+     */
+    private Integer gotResult;
+
+    /**
+     * 是否拥有回调函数(0否 1是)
+     */
+    private Integer hadCallback;
+
+    /**
+     * 结果(对象json字符串)
+     */
+    private String sendResultParam;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 10 - 17
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsLogDO.java → src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsSendLogDO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
 
+import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -17,8 +18,8 @@ import java.util.Date;
 @Data
 @EqualsAndHashCode
 @Accessors(chain = true)
-@TableName(value = "sms_log", autoResultMap = true)
-public class SysSmsLogDO implements Serializable {
+@TableName(value = "sms_send_log", autoResultMap = true)
+public class SysSmsSendLogDO implements Serializable {
 
     /**
      * 自增编号
@@ -41,14 +42,9 @@ public class SysSmsLogDO implements Serializable {
     private String templateCode;
 
     /**
-     * 手机号(数组json字符串)
+     * 手机号
      */
-    private String phones;
-
-    /**
-     * 内容
-     */
-    private String content;
+    private String phone;
 
     /**
      * 备注
@@ -56,18 +52,15 @@ public class SysSmsLogDO implements Serializable {
     private String remark;
 
     /**
-     * 发送状态(1异步推送中 2发送中 3失败 4成功)
+     * 发送状态
+     *
+     * @see SmsSendStatusEnum
      */
     private Integer sendStatus;
 
     /**
-     * 创建者
-     */
-    private String createBy;
-
-    /**
-     * 创建时间
+     * 发送时间
      */
-    private Date createTime;
+    private Date sendTime;
 
 }

+ 15 - 9
src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java

@@ -13,20 +13,26 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum SmsSendStatusEnum {
 
+    //请求发送结果时失败
+    QUERY_SEND_FAIL(-3),
+
+    //短信发送失败
+    SEND_FAIL(-2),
+
+    //短信请求失败
+    QUERY_FAIL(-1),
+
     //异步转发中
-    ASYNC(1),
+    ASYNC(0),
 
-    //发送中
-    SENDING(2),
+    //请求成功
+    QUERY_SUCCESS(1),
 
-    //失败
-    FAIL(3),
+    //短信成功
+    SEND_SUCCESS(2),
 
     //等待回执
-    WAITING(4),
-
-    //成功
-    SUCCESS(5);
+    WAITING(3);
 
     private final int status;
 

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

@@ -1,10 +1,12 @@
 package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
 
 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
 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.SysSmsService;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -20,12 +22,20 @@ import javax.annotation.Resource;
 public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> {
 
     @Resource
-    private SysSmsService sysSmsService;
+    private SysSmsChannelService smsChannelService;
+
+    @Resource
+    private SysSmsQueryLogService smsQueryLogService;
 
     @Override
     public void onMessage(SmsSendMessage message) {
-        log.info("[onMessage][收到 发送短信 消息], content: " +  message.toString());
-        SmsResult send = sysSmsService.send(message.getSmsBody(), message.getTargetPhones());
+        log.info("[onMessage][收到 发送短信 消息], content: " + message.toString());
+        AbstractSmsClient smsClient = smsChannelService.getSmsClient(message.getSmsBody().getTemplateCode());
+        String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(message.getSmsBody().getTemplateCode());
+
+        SmsResult result = smsClient.send(templateApiId, message.getSmsBody(), message.getTargetPhones());
+
+        smsQueryLogService.afterSendLog(message.getSmsBody().getSmsLogId(), result);
     }
 
 }

+ 3 - 4
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsLogService.java → src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java

@@ -7,26 +7,25 @@ import cn.iocoder.dashboard.framework.sms.core.SmsResult;
 import java.util.List;
 
 /**
- * 短信渠道Service接口
+ * 短信请求日志服务接口
  *
  * @author zzf
  * @date 2021/1/25 9:24
  */
-public interface SysSmsLogService {
+public interface SysSmsQueryLogService {
     /**
      * 发送短信前的日志处理
      *
      * @param smsBody      短信内容
      * @param targetPhones 发送对象手机号集合
      * @param client       短信客户端
-     * @param isAsync      是否异步发送
      * @return 生成的日志id
      */
     // TODO FROM 芋艿 to ZZF: async 是针对发送的方式,对于日志不一定需要关心。这样,短信日志,实际就发送前插入,发送后更新结果.
     //   这里只用于记录状态,毕竟异步可能推送失败,此时日志可记录该状态。
 
     // TODO FROM 芋艿 to ZZF:短信日志,群发的情况,应该是每个手机一条哈。虽然是群发,但是可能部分成功,部分失败;对应到短信平台,实际也是多条。
-    Long beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client, Boolean isAsync);
+    void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client);
 
     /**
      * 发送消息后的日志处理

+ 13 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsSendLogService.java

@@ -0,0 +1,13 @@
+package cn.iocoder.dashboard.modules.system.service.sms;
+
+/**
+ * 短信发送日志服务接口
+ *
+ * @author zzf
+ * @date 13:48 2021/3/2
+ */
+public interface SysSmsSendLogService {
+
+    void getAndSaveSmsSendLog();
+
+}

+ 11 - 58
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java

@@ -1,15 +1,15 @@
 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 javax.servlet.ServletRequest;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 /**
  * 短信Service接口
+ * 只支持异步,因此没有返回值
  *
  * @author zzf
  * @date 2021/1/25 9:24
@@ -21,23 +21,17 @@ public interface SysSmsService {
      *
      * @param smsBody      消息内容
      * @param targetPhones 发送对象手机号列表
-     * @return 是否发送成功
      */
-    SmsResult send(SmsBody smsBody, List<String> targetPhones);
+    void send(SmsBody smsBody, List<String> 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));
+    default void send(SmsBody smsBody, String targetPhone) {
+        send(smsBody, Collections.singletonList(targetPhone));
     }
 
     /**
@@ -45,57 +39,16 @@ public interface SysSmsService {
      *
      * @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));
+    default void send(SmsBody smsBody, String... targetPhones) {
+        send(smsBody, Arrays.asList(targetPhones));
     }
 
-
     /**
-     * 异步发送消息
+     * 处理短信发送回调函数
      *
-     * @param msgBody      消息内容
-     * @param targetPhones 发送对象列表
+     * @param request 请求
+     * @return 响应数据
      */
-    void sendAsync(SmsBody msgBody, List<String> 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;
-    }
-
+    Object smsSendCallbackHandle(ServletRequest request);
 }

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

@@ -1,77 +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.property.SmsChannelProperty;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsLogMapper;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsLogDO;
-import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
-import cn.iocoder.dashboard.util.json.JsonUtils;
-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 SysSmsLogServiceImpl implements SysSmsLogService {
-
-    @Resource
-    private SysSmsLogMapper logMapper;
-
-    @Override
-    public Long beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client, Boolean isAsync) {
-        SysSmsLogDO smsLog = new SysSmsLogDO();
-        if (smsBody.getSmsLogId() != null) {
-            smsLog.setId(smsBody.getSmsLogId());
-            smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus());
-            logMapper.updateById(smsLog);
-            return smsBody.getSmsLogId();
-        } else {
-            SmsChannelProperty property = client.getProperty();
-
-            smsLog.setChannelCode(property.getCode())
-                    .setChannelId(property.getId())
-                    .setTemplateCode(smsBody.getTemplateCode())
-                    .setPhones(JsonUtils.toJsonString(targetPhones))
-                    .setContent(smsBody.getParams().toString());
-
-            if (isAsync) {
-                smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus());
-            } else {
-                smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus());
-            }
-            logMapper.insert(smsLog);
-            return smsLog.getId();
-        }
-    }
-
-    @Override
-    public void afterSendLog(Long logId, SmsResult result) {
-        SysSmsLogDO smsLog = new SysSmsLogDO();
-        smsLog.setId(logId);
-        if (result.getSuccess()) {
-            smsLog.setSendStatus(SmsSendStatusEnum.SUCCESS.getStatus());
-            SysSmsLogDO smsLogDO = logMapper.selectById(logId);
-            result.getResult().forEach(s -> {
-                smsLogDO.setPhones(s.getPhone());
-                smsLogDO.setSendStatus(s.getStatus());
-                smsLogDO.setRemark(s.getMessage());
-                smsLogDO.setCreateTime(s.getCreateTime());
-                logMapper.insert(smsLogDO);
-            });
-        } else {
-            smsLog.setSendStatus(SmsSendStatusEnum.FAIL.getStatus());
-            smsLog.setRemark(result.getMessage() + JsonUtils.toJsonString(result.getResult()));
-        }
-        logMapper.updateById(smsLog);
-    }
-
-}

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

@@ -0,0 +1,59 @@
+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.property.SmsChannelProperty;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
+import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
+import cn.iocoder.dashboard.util.json.JsonUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 短信请求日志服务实现类
+ *
+ * @author zzf
+ * @date 13:50 2021/3/2
+ */
+@Service
+public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
+
+    @Resource
+    private SysSmsQueryLogMapper logMapper;
+
+    @Override
+    public void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client) {
+        SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
+        SmsChannelProperty property = client.getProperty();
+
+        smsLog.setChannelCode(property.getCode())
+                .setChannelId(property.getId())
+                .setTemplateCode(smsBody.getTemplateCode())
+                .setPhones(targetPhones)
+                .setContent(smsBody.getParams().toString());
+
+        smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus());
+        logMapper.insert(smsLog);
+        smsBody.setSmsLogId(smsLog.getId());
+    }
+
+    @Override
+    public void afterSendLog(Long logId, SmsResult result) {
+        SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
+        smsLog.setId(logId);
+        if (result.getSuccess()) {
+            smsLog.setSendStatus(SmsSendStatusEnum.QUERY_SUCCESS.getStatus());
+            smsLog.setSendResultParam(result.getSendResultParam());
+        } else {
+            smsLog.setSendStatus(SmsSendStatusEnum.QUERY_FAIL.getStatus());
+            smsLog.setRemark(result.getMessage());
+        }
+        logMapper.updateById(smsLog);
+    }
+
+}

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

@@ -0,0 +1,109 @@
+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.framework.sms.client.NeedQuerySendResultSmsClient;
+import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsSendLogMapper;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsSendLogDO;
+import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
+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;
+
+/**
+ * 短信发送日志服务实现类
+ *
+ * @author zzf
+ * @date 2021/1/25 9:25
+ */
+@Slf4j
+@Service
+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 void getAndSaveSmsSendLog() {
+
+        List<SysSmsQueryLogDO> noResultQueryLogList = smsQueryLogMapper.selectNoResultQueryLogList();
+
+        if (CollectionUtil.isEmpty(noResultQueryLogList)) {
+            return;
+        }
+        //用于添加的发送日志对象
+        SysSmsSendLogDO insertSendLog = new SysSmsSendLogDO();
+        //用于修改状态的请求日志对象
+        SysSmsQueryLogDO updateQueryLog = new SysSmsQueryLogDO();
+
+        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(SmsSendStatusEnum.SEND_SUCCESS.getStatus());
+            updateQueryLog.setRemark(String.format("日志(id = %s)对应的客户端没有继承NeedQuerySendResultSmsClient, 不能获取短信结果。", queryLog.getId()));
+            smsQueryLogMapper.updateById(updateQueryLog);
+        });
+    }
+
+    private void queryLog2SendLong(SysSmsSendLogDO insertSendLog, SysSmsQueryLogDO queryLog) {
+        insertSendLog.setChannelCode(queryLog.getChannelCode());
+        insertSendLog.setChannelId(queryLog.getChannelId());
+        insertSendLog.setTemplateCode(queryLog.getTemplateCode());
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void getSmsSendResultJob() {
+        getAndSaveSmsSendLog();
+    }
+}

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

@@ -2,11 +2,11 @@ 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.SysSmsChannelService;
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -25,30 +25,18 @@ public class SysSmsServiceImpl implements SysSmsService {
     private SysSmsChannelService channelService;
 
     @Resource
-    private SysSmsLogService logService;
+    private SysSmsQueryLogService logService;
 
     @Resource
     private SmsProducer smsProducer;
 
     @Override
-    public SmsResult send(SmsBody smsBody, List<String> targetPhones) {
+    public void send(SmsBody smsBody, List<String> targetPhones) {
         AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode());
-        String templateApiId = channelService.getSmsTemplateApiIdByCode(smsBody.getTemplateCode());
-        Long logId = logService.beforeSendLog(smsBody, targetPhones, client, false);
-
-        SmsResult result = client.send(templateApiId, smsBody, targetPhones);
-
-        logService.afterSendLog(logId, result);
-
-        return result;
-    }
-
-    // TODO FROM 芋艿 to ZZF:可能要讨论下,对于短信发送来说,貌似只提供异步发送即可。对于业务来说,一定不能依赖短信的发送结果。
-    //  我的想法是1、很多短信,比如验证码,总还是需要知道是否发送成功的。2、别人可以不用,我们不能没有。3、实现挺简单的,个人觉得无需纠结。
-    @Override
-    public void sendAsync(SmsBody smsBody, List<String> targetPhones) {
-        AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode());
-        logService.beforeSendLog(smsBody, targetPhones, client, true);
+        logService.beforeSendLog(smsBody, targetPhones, client);
         smsProducer.sendSmsSendMessage(smsBody, targetPhones);
     }
+
+    // TODO FROM 芋艿 to ZZF:可能要讨论下,对于短信发送来说,貌似只提供异步发送即可。对于业务来说,一定不能依赖短信的发送结果.
+
 }

+ 9 - 1
src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java

@@ -20,7 +20,7 @@ public class JsonUtils {
 
     /**
      * 初始化 objectMapper 属性
-     *
+     * <p>
      * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
      *
      * @param objectMapper ObjectMapper 对象
@@ -67,4 +67,12 @@ public class JsonUtils {
         }
     }
 
+    public static <T> T parseByType(String text, TypeReference<T> typeReference) {
+        try {
+            return objectMapper.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
 }