Commit 767cd90279e2247e704156f7c028b0b97560f663

Authored by zengzefeng
1 parent c3372d4b

批量发送半成品

Showing 29 changed files with 693 additions and 188 deletions
... ... @@ -184,12 +184,6 @@
184 184 </dependency>
185 185  
186 186 <dependency>
187   - <groupId>org.projectlombok</groupId>
188   - <artifactId>lombok</artifactId>
189   - <version>${lombok.version}</version>
190   - </dependency>
191   -
192   - <dependency>
193 187 <groupId>org.mapstruct</groupId>
194 188 <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
195 189 <version>${mapstruct.version}</version>
... ... @@ -222,17 +216,25 @@
222 216 <version>${easyexcel.verion}</version>
223 217 </dependency>
224 218  
  219 +
  220 + <!-- SMS SDK begin -->
  221 + <dependency>
  222 + <groupId>com.yunpian.sdk</groupId>
  223 + <artifactId>yunpian-java-sdk</artifactId>
  224 + <version>1.2.7</version>
  225 + </dependency>
  226 +
225 227 <dependency>
226 228 <groupId>com.aliyun</groupId>
227 229 <artifactId>aliyun-java-sdk-core</artifactId>
228 230 <version>4.5.18</version>
229 231 </dependency>
230   -
231 232 <dependency>
232 233 <groupId>com.aliyun</groupId>
233 234 <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
234 235 <version>2.1.0</version>
235 236 </dependency>
  237 + <!-- SMS SDK end -->
236 238  
237 239 </dependencies>
238 240  
... ...
sql/sms.sql
... ... @@ -12,6 +12,8 @@ CREATE TABLE `sms_channel`
12 12 `code` varchar(50) NOT NULL COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
13 13 `api_key` varchar(100) NOT NULL COMMENT '账号id',
14 14 `api_secret` varchar(100) NOT NULL COMMENT '账号秘钥',
  15 + `had_callback` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否拥有回调函数',
  16 + `callback_url` varchar(100) NOT NULL default '' COMMENT '回调请求路径',
15 17 `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
16 18 `name` varchar(50) NOT NULL COMMENT '名称',
17 19 `signature` varchar(50) NOT NULL COMMENT '签名值',
... ... @@ -61,22 +63,46 @@ CREATE TABLE `sms_template`
61 63 DEFAULT CHARSET = utf8mb4 COMMENT ='短信模板';
62 64  
63 65 -- ----------------------------
  66 +-- Table structure for sms_query_log
  67 +-- ----------------------------
  68 +DROP TABLE IF EXISTS `sms_query_log`;
  69 +CREATE TABLE `sms_query_log`
  70 +(
  71 + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  72 + `api_id` varchar(100) NOT NULL COMMENT '第三方唯一标识',
  73 + `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)',
  74 + `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id',
  75 + `template_code` varchar(50) NOT NULL COMMENT '渠道编码',
  76 + `phones` varchar(2000) NOT NULL COMMENT '手机号(数组json字符串)',
  77 + `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
  78 + `send_result_param` varchar(200) NOT NULL DEFAULT '' COMMENT '查询短信发送结果的参数',
  79 + `send_status` tinyint(1) NOT NULL DEFAULT 2 COMMENT '发送状态(0本地异步中 1发送请求失败 2发送请求成功)',
  80 + `got_result` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否获取发送结果',
  81 + `had_callback` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否拥有回调函数',
  82 + `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者',
  83 + `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  84 + PRIMARY KEY (`id`) USING BTREE
  85 +) ENGINE = InnoDB
  86 + AUTO_INCREMENT = 1
  87 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信请求日志';
  88 +
  89 +-- ----------------------------
64 90 -- Table structure for sms_log
65 91 -- ----------------------------
66   -DROP TABLE IF EXISTS `sms_log`;
67   -CREATE TABLE `sms_log`
  92 +DROP TABLE IF EXISTS `sms_send_log`;
  93 +CREATE TABLE `sms_send_log`
68 94 (
69 95 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
70 96 `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)',
71 97 `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id',
72 98 `template_code` varchar(50) NOT NULL COMMENT '渠道编码',
73   - `phones` char(11) NOT NULL COMMENT '手机号(数组json字符串)',
  99 + `query_log_id` bigint(20) NOT NULL COMMENT '请求日志id',
  100 + `phone` char(11) NOT NULL COMMENT '手机号',
74 101 `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
75   - `remark` varchar(200) DEFAULT NULL COMMENT '备注',
76   - `send_status` tinyint(4) NOT NULL DEFAULT 2 COMMENT '发送状态(1异步推送中 2发送中 3失败 4成功)',
77   - `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者',
78   - `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  102 + `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  103 + `success` tinyint(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  104 + `send_time` datetime DEFAULT NULL COMMENT '创建时间',
79 105 PRIMARY KEY (`id`) USING BTREE
80 106 ) ENGINE = InnoDB
81 107 AUTO_INCREMENT = 1
82   - DEFAULT CHARSET = utf8mb4 COMMENT ='短信日志';
  108 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信发送日志';
... ...
src/main/java/cn/iocoder/dashboard/common/enums/DefaultBitFieldEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 通用状态枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum DefaultBitFieldEnum {
  14 +
  15 + NO(0, "否"),
  16 + YES(1, "是");
  17 +
  18 + /**
  19 + * 状态值
  20 + */
  21 + private final Integer val;
  22 + /**
  23 + * 状态名
  24 + */
  25 + private final String name;
  26 +
  27 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java
... ... @@ -58,7 +58,7 @@ public abstract class AbstractSmsClient implements SmsClient {
58 58 * @return 短信发送结果
59 59 * @throws Exception 调用发送失败,抛出异常
60 60 */
61   - public abstract SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception;
  61 + protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception;
62 62  
63 63 protected void beforeSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception {
64 64 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.dashboard.framework.sms.client;
2 2  
3 3 import cn.hutool.core.date.DateUtil;
4 4 import cn.hutool.core.util.ArrayUtil;
5   -import cn.hutool.core.util.StrUtil;
6 5 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
7 6 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
8 7 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
... ... @@ -14,11 +13,11 @@ import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
14 13 import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
15 14 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
16 15 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
  16 +import com.aliyuncs.exceptions.ClientException;
17 17 import com.aliyuncs.http.MethodType;
18 18 import com.aliyuncs.profile.DefaultProfile;
19 19 import com.aliyuncs.profile.IClientProfile;
20 20 import lombok.extern.slf4j.Slf4j;
21   -import org.apache.commons.lang3.StringUtils;
22 21  
23 22 import java.util.ArrayList;
24 23 import java.util.Collection;
... ... @@ -31,7 +30,7 @@ import java.util.List;
31 30 * @date 2021/1/25 14:17
32 31 */
33 32 @Slf4j
34   -public class AliyunSmsClient extends AbstractSmsClient {
  33 +public class AliyunSmsClient extends AbstractSmsClient implements NeedQuerySendResultSmsClient {
35 34  
36 35 private static final String OK = "OK";
37 36  
... ... @@ -70,35 +69,43 @@ public class AliyunSmsClient extends AbstractSmsClient {
70 69 request.setTemplateParam(smsBody.getParamsStr());
71 70 SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
72 71  
73   - boolean result = OK.equals(sendSmsResponse.getCode());
74   - if (!result) {
  72 + boolean success = OK.equals(sendSmsResponse.getCode());
  73 + if (!success) {
75 74 log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
76 75 }
77   - SmsResult resultBody = new SmsResult();
78   - resultBody.setSuccess(result);
  76 + return new SmsResult()
  77 + .setSuccess(success)
  78 + .setMessage(sendSmsResponse.getMessage())
  79 + .setCode(sendSmsResponse.getCode())
  80 + .setApiId(sendSmsResponse.getBizId())
  81 + .setSendResultParam(sendSmsResponse.getBizId());
  82 + }
  83 +
  84 +
  85 + @Override
  86 + public List<SmsResultDetail> getSmsSendResult(String param) throws ClientException {
79 87 QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
80   - querySendDetailsRequest.setBizId(sendSmsResponse.getBizId());
81   - // TODO FROM 芋艿 to zzf:发送完之后,基于短信平台回调,去更新回执状态。短信发送是否成功,和最终用户收到,是两个维度。这块有困惑,可以微信,我给个截图哈。
  88 + querySendDetailsRequest.setBizId(param);
  89 + // TODO FROM 芋艿 to zzf:发送完之后,基于短信平台回调,去更新回执状态。短信发送是否成功,和最终用户收到,是两个维度。这块有困惑,可以微信,我给个截图哈。 DONE
82 90 QuerySendDetailsResponse acsResponse = acsClient.getAcsResponse(querySendDetailsRequest);
83 91 List<SmsResultDetail> resultDetailList = new ArrayList<>(Integer.parseInt(acsResponse.getTotalCount()));
84 92 acsResponse.getSmsSendDetailDTOs().forEach(s -> {
85 93 SmsResultDetail resultDetail = new SmsResultDetail();
86   - resultDetail.setCreateTime(DateUtil.parseDateTime(s.getSendDate()));
  94 + resultDetail.setSendTime(DateUtil.parseDateTime(s.getSendDate()));
87 95 resultDetail.setMessage(s.getContent());
88 96 resultDetail.setPhone(s.getPhoneNum());
89   - resultDetail.setStatus(statusConvert(s.getSendStatus()));
  97 + resultDetail.setSendStatus(statusConvert(s.getSendStatus()));
90 98 resultDetailList.add(resultDetail);
91 99 });
92   - resultBody.setResult(resultDetailList);
93   - return resultBody;
  100 + return resultDetailList;
94 101 }
95 102  
96 103 private int statusConvert(Long aliSendStatus) {
97 104 if (aliSendStatus == 1L) {
98   - return SmsSendStatusEnum.SUCCESS.getStatus();
  105 + return SmsSendStatusEnum.SEND_SUCCESS.getStatus();
99 106 }
100 107 if (aliSendStatus == 2L) {
101   - return SmsSendStatusEnum.FAIL.getStatus();
  108 + return SmsSendStatusEnum.SEND_FAIL.getStatus();
102 109 }
103 110 return SmsSendStatusEnum.WAITING.getStatus();
104 111 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/HadCallbackSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.client;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  4 +
  5 +import javax.servlet.ServletRequest;
  6 +import java.io.UnsupportedEncodingException;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 需要发送请求获取短信发送结果的短信客户端
  11 + *
  12 + * @author zzf
  13 + * @date 2021/3/4 17:20
  14 + */
  15 +public interface HadCallbackSmsClient {
  16 +
  17 + /**
  18 + * 获取短信发送结果
  19 + *
  20 + * @param request 请求
  21 + * @return 短信发送结果
  22 + */
  23 + List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws Exception;
  24 +
  25 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/NeedQuerySendResultSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.client;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  4 +import com.aliyuncs.exceptions.ClientException;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 需要发送请求获取短信发送结果的短信客户端
  10 + *
  11 + * @author zzf
  12 + * @date 2021/3/4 17:20
  13 + */
  14 +public interface NeedQuerySendResultSmsClient {
  15 +
  16 + /**
  17 + * 获取短信发送结果
  18 + *
  19 + * @param param 参数
  20 + * @return 短信发送结果
  21 + */
  22 + List<SmsResultDetail> getSmsSendResult(String param) throws Exception;
  23 +
  24 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java
... ... @@ -2,8 +2,10 @@ package cn.iocoder.dashboard.framework.sms.client;
2 2  
3 3 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
5 6  
6 7 import java.util.Collection;
  8 +import java.util.List;
7 9  
8 10 /**
9 11 * 短信父接口
... ... @@ -23,4 +25,7 @@ public interface SmsClient {
23 25 */
24 26 SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets);
25 27  
  28 +
  29 + //List<SmsResultDetail> getSmsSendResult(String jsonObjectParam);
  30 +
26 31 }
27 32 \ No newline at end of file
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/YunpianSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.client;
  2 +
  3 +import cn.hutool.core.date.DateUtil;
  4 +import cn.hutool.core.util.CharsetUtil;
  5 +import cn.iocoder.dashboard.framework.sms.core.SmsBody;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsConstants;
  7 +import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  8 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  9 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
  10 +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
  11 +import cn.iocoder.dashboard.util.json.JsonUtils;
  12 +import com.fasterxml.jackson.core.type.TypeReference;
  13 +import com.yunpian.sdk.YunpianClient;
  14 +import com.yunpian.sdk.constant.Code;
  15 +import com.yunpian.sdk.constant.YunpianConstant;
  16 +import com.yunpian.sdk.model.Result;
  17 +import com.yunpian.sdk.model.SmsBatchSend;
  18 +import lombok.extern.slf4j.Slf4j;
  19 +
  20 +import javax.servlet.ServletRequest;
  21 +import java.io.UnsupportedEncodingException;
  22 +import java.net.URLEncoder;
  23 +import java.util.*;
  24 +
  25 +/**
  26 + * 云片短信实现类
  27 + *
  28 + * @author zzf
  29 + * @date 9:48 2021/3/5
  30 + */
  31 +@Slf4j
  32 +public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSmsClient {
  33 +
  34 + private final YunpianClient client;
  35 +
  36 + private final TypeReference<List<Map<String, String>>> callbackType = new TypeReference<List<Map<String, String>>>() {
  37 + };
  38 +
  39 + /**
  40 + * 构造云片短信发送处理
  41 + *
  42 + * @param channelVO 阿里云短信配置
  43 + */
  44 + public YunpianSmsClient(SmsChannelProperty channelVO) {
  45 + super(channelVO);
  46 + client = new YunpianClient(channelVO.getApiKey());
  47 + }
  48 +
  49 + @Override
  50 + public SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) {
  51 + Map<String, String> paramMap = new HashMap<>();
  52 + paramMap.put("apikey", getProperty().getApiKey());
  53 + paramMap.put("mobile", String.join(SmsConstants.COMMA, targets));
  54 + paramMap.put("text", formatContent(smsBody));
  55 + paramMap.put("callback", getProperty().getCallbackUrl());
  56 +
  57 + Result<SmsBatchSend> sendResult = client.sms().batch_send(paramMap);
  58 + boolean success = sendResult.getCode().equals(Code.OK);
  59 +
  60 + if (!success) {
  61 + log.debug("send fail[code={}, message={}]", sendResult.getCode(), sendResult.getDetail());
  62 + }
  63 + return new SmsResult()
  64 + .setSuccess(success)
  65 + .setMessage(sendResult.getDetail())
  66 + .setCode(sendResult.getCode().toString())
  67 + .setApiId(sendResult.getData().getData().get(0).getSid().toString());
  68 + }
  69 +
  70 +
  71 + /**
  72 + * 格式化短信内容,将参数注入到模板中
  73 + *
  74 + * @param smsBody 短信信息
  75 + * @return 格式化后的短信内容
  76 + */
  77 + private String formatContent(SmsBody smsBody) {
  78 + StringBuilder result = new StringBuilder(smsBody.getTemplateContent());
  79 + smsBody.getParams().forEach((key, val) -> {
  80 + String param = parseParamToPlaceholder(key);
  81 + result.replace(result.indexOf(param), result.indexOf(param + param.length()), val);
  82 + });
  83 + return result.toString();
  84 + }
  85 +
  86 + /**
  87 + * 将指定参数改成对应的占位字符
  88 + * <p>
  89 + * 云片的是 #param# 的形式作为占位符
  90 + *
  91 + * @param key 参数名
  92 + * @return 对应的占位字符
  93 + */
  94 + private String parseParamToPlaceholder(String key) {
  95 + return SmsConstants.JING_HAO + key + SmsConstants.JING_HAO;
  96 + }
  97 +
  98 +
  99 + @Override
  100 + public List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws UnsupportedEncodingException {
  101 + List<Map<String, String>> stringStringMap = getSendResult(request);
  102 + List<SmsResultDetail> resultDetailList = new ArrayList<>(stringStringMap.size());
  103 + stringStringMap.forEach(map -> {
  104 + SmsResultDetail detail = new SmsResultDetail();
  105 +
  106 + detail.setPhone(map.get("mobile"));
  107 + detail.setMessage(map.get("error_msg"));
  108 + detail.setSendTime(DateUtil.parseTime(map.get("user_receive_time")));
  109 + String reportStatus = map.get("report_status");
  110 + detail.setSendStatus(reportStatus.equals(SmsConstants.SUCCESS)
  111 + ? SmsSendStatusEnum.SEND_SUCCESS.getStatus()
  112 + : SmsSendStatusEnum.SEND_FAIL.getStatus()
  113 + );
  114 + resultDetailList.add(detail);
  115 + });
  116 + return resultDetailList;
  117 + }
  118 +
  119 + /**
  120 + * 从 request 中获取请求中传入的短信发送结果信息
  121 + *
  122 + * @param request 回调请求
  123 + * @return 短信发送结果信息
  124 + * @throws UnsupportedEncodingException 解码异常
  125 + */
  126 + private List<Map<String, String>> getSendResult(ServletRequest request) throws UnsupportedEncodingException {
  127 + Map<String, String[]> parameterMap = request.getParameterMap();
  128 + String[] smsStatuses = parameterMap.get(YunpianConstant.SMS_STATUS);
  129 + String encode = URLEncoder.encode(smsStatuses[0], CharsetUtil.UTF_8);
  130 + return JsonUtils.parseByType(encode, callbackType);
  131 + }
  132 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsBody.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.dashboard.util.json.JsonUtils;
4 4 import lombok.Data;
5 5  
6 6 import java.util.Map;
7   -import java.util.UUID;
8 7  
9 8 /**
10 9 * 消息内容实体类
... ... @@ -23,6 +22,11 @@ public class SmsBody {
23 22 private String templateCode;
24 23  
25 24 /**
  25 + * 模板编码
  26 + */
  27 + private String templateContent;
  28 +
  29 + /**
26 30 * 参数列表
27 31 */
28 32 private Map<String, String> params;
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core;
  2 +
  3 +/**
  4 + * 短信相关常量类
  5 + *
  6 + * @author zzf
  7 + * @date 2021/3/5 10:42
  8 + */
  9 +public interface SmsConstants {
  10 +
  11 + String OK = "OK";
  12 +
  13 + String JING_HAO = "#";
  14 +
  15 + String COMMA = ",";
  16 +
  17 + String SUCCESS = "SUCCESS";
  18 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java
1 1 package cn.iocoder.dashboard.framework.sms.core;
2 2  
3 3 import lombok.Data;
  4 +import lombok.experimental.Accessors;
4 5  
5 6 import java.io.Serializable;
6   -import java.util.List;
7 7  
8 8 /**
9 9 * 消息内容实体类
10 10 */
11 11 @Data
  12 +@Accessors(chain = true)
12 13 public class SmsResult implements Serializable {
13 14  
14 15 /**
... ... @@ -17,6 +18,11 @@ public class SmsResult implements Serializable {
17 18 private Boolean success;
18 19  
19 20 /**
  21 + * 第三方唯一标识
  22 + */
  23 + private String apiId;
  24 +
  25 + /**
20 26 * 状态码
21 27 */
22 28 private String code;
... ... @@ -27,10 +33,9 @@ public class SmsResult implements Serializable {
27 33 private String message;
28 34  
29 35 /**
30   - * 返回值
  36 + * 用于查询发送结果的参数
31 37 */
32   - private List<SmsResultDetail> result;
33   -
  38 + private String sendResultParam;
34 39  
35 40 public static SmsResult failResult(String message) {
36 41 SmsResult resultBody = new SmsResult();
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResultDetail.java
... ... @@ -14,7 +14,7 @@ public class SmsResultDetail implements Serializable {
14 14 /**
15 15 * 短信发送状态 {@link cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum}
16 16 */
17   - private Integer status;
  17 + private Integer sendStatus;
18 18  
19 19 /**
20 20 * 接收手机号
... ... @@ -29,5 +29,5 @@ public class SmsResultDetail implements Serializable {
29 29 /**
30 30 * 时间
31 31 */
32   - private Date createTime;
  32 + private Date sendTime;
33 33 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperty.java
... ... @@ -54,4 +54,15 @@ public class SmsChannelProperty implements Serializable {
54 54 @NotEmpty(message = "签名值不能为空")
55 55 private String signature;
56 56  
  57 + /**
  58 + * 是否拥有回调函数(0否 1是)
  59 + */
  60 + @NotNull(message = "是否拥有回调函数不能为空")
  61 + private Integer hadCallback;
  62 +
  63 + /**
  64 + * 短信发送回调url
  65 + */
  66 + private String callbackUrl;
  67 +
57 68 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  4 +import org.springframework.web.bind.annotation.RequestMapping;
  5 +import org.springframework.web.bind.annotation.RestController;
  6 +
  7 +import javax.annotation.Resource;
  8 +import javax.servlet.ServletRequest;
  9 +
  10 +/**
  11 + * 短信默认回调接口
  12 + *
  13 + * @author zzf
  14 + * @date 2021/3/5 8:59
  15 + */
  16 +@RestController("/sms/callback")
  17 +public class SmsDefaultCallbackController {
  18 +
  19 + @Resource
  20 + private SysSmsService smsService;
  21 +
  22 + @RequestMapping("/sms-send")
  23 + public Object sendSmsCallback(ServletRequest request){
  24 + return smsService.smsSendCallbackHandle(request);
  25 + }
  26 +
  27 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsQueryLogMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.enums.DefaultBitFieldEnum;
  4 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
  5 +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
  6 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  7 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +@Mapper
  13 +public interface SysSmsQueryLogMapper extends BaseMapper<SysSmsQueryLogDO> {
  14 +
  15 + /**
  16 + * 查询还没有获取发送结果的短信请求信息
  17 + *
  18 + * @return
  19 + */
  20 + default List<SysSmsQueryLogDO> selectNoResultQueryLogList() {
  21 + return this.selectList(new LambdaQueryWrapper<SysSmsQueryLogDO>()
  22 + .eq(SysSmsQueryLogDO::getSendStatus, SmsSendStatusEnum.QUERY_SUCCESS)
  23 + .eq(SysSmsQueryLogDO::getGotResult, DefaultBitFieldEnum.NO)
  24 + .eq(SysSmsQueryLogDO::getHadCallback, DefaultBitFieldEnum.NO)
  25 + );
  26 + }
  27 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsLogMapper.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsSendLogMapper.java
1 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms;
2 2  
3   -import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsLogDO;
  3 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsSendLogDO;
4 4 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 5 import org.apache.ibatis.annotations.Mapper;
6 6  
7 7 @Mapper
8   -public interface SysSmsLogMapper extends BaseMapper<SysSmsLogDO> {
  8 +public interface SysSmsSendLogMapper extends BaseMapper<SysSmsSendLogDO> {
9 9  
10 10 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsChannelDO.java
... ... @@ -28,6 +28,16 @@ public class SysSmsChannelDO extends BaseDO {
28 28 private String code;
29 29  
30 30 /**
  31 + * 是否拥有回答(0否 1是)
  32 + */
  33 + private Integer had_callback;
  34 +
  35 + /**
  36 + * 短信发送回调url
  37 + */
  38 + private String callback_url;
  39 +
  40 + /**
31 41 * 渠道账号id
32 42 */
33 43 private String apiKey;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsLogDO.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsQueryLogDO.java
1 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
2 2  
  3 +import com.baomidou.mybatisplus.annotation.TableField;
3 4 import com.baomidou.mybatisplus.annotation.TableName;
  5 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
4 6 import lombok.Data;
5 7 import lombok.EqualsAndHashCode;
6 8 import lombok.experimental.Accessors;
7 9  
8 10 import java.io.Serializable;
9 11 import java.util.Date;
  12 +import java.util.List;
10 13  
11 14 /**
12 15 * 短信日志
... ... @@ -17,8 +20,8 @@ import java.util.Date;
17 20 @Data
18 21 @EqualsAndHashCode
19 22 @Accessors(chain = true)
20   -@TableName(value = "sms_log", autoResultMap = true)
21   -public class SysSmsLogDO implements Serializable {
  23 +@TableName(value = "sms_query_log", autoResultMap = true)
  24 +public class SysSmsQueryLogDO implements Serializable {
22 25  
23 26 /**
24 27 * 自增编号
... ... @@ -41,9 +44,10 @@ public class SysSmsLogDO implements Serializable {
41 44 private String templateCode;
42 45  
43 46 /**
44   - * 手机号(数组json字符串)
  47 + * 手机号
45 48 */
46   - private String phones;
  49 + @TableField(typeHandler = JacksonTypeHandler.class)
  50 + private List<String> phones;
47 51  
48 52 /**
49 53 * 内容
... ... @@ -51,17 +55,34 @@ public class SysSmsLogDO implements Serializable {
51 55 private String content;
52 56  
53 57 /**
54   - * 备注
  58 + * 发送状态
  59 + *
  60 + * @see cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum
55 61 */
56   - private String remark;
  62 + private Integer sendStatus;
57 63  
58 64 /**
59   - * 发送状态(1异步推送中 2发送中 3失败 4成功)
  65 + * 是否获取过结果[0否 1是]
60 66 */
61   - private Integer sendStatus;
  67 + private Integer gotResult;
  68 +
  69 + /**
  70 + * 是否拥有回调函数(0否 1是)
  71 + */
  72 + private Integer hadCallback;
  73 +
  74 + /**
  75 + * 结果(对象json字符串)
  76 + */
  77 + private String sendResultParam;
  78 +
  79 + /**
  80 + * 备注
  81 + */
  82 + private String remark;
62 83  
63 84 /**
64   - * 创建者
  85 + * 创建人
65 86 */
66 87 private String createBy;
67 88  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsSendLogDO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
  4 +import com.baomidou.mybatisplus.annotation.TableName;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.experimental.Accessors;
  8 +
  9 +import java.io.Serializable;
  10 +import java.util.Date;
  11 +
  12 +/**
  13 + * 短信日志
  14 + *
  15 + * @author zzf
  16 + * @since 2021-01-25
  17 + */
  18 +@Data
  19 +@EqualsAndHashCode
  20 +@Accessors(chain = true)
  21 +@TableName(value = "sms_send_log", autoResultMap = true)
  22 +public class SysSmsSendLogDO implements Serializable {
  23 +
  24 + /**
  25 + * 自增编号
  26 + */
  27 + private Long id;
  28 +
  29 + /**
  30 + * 短信渠道编码(来自枚举类)
  31 + */
  32 + private String channelCode;
  33 +
  34 + /**
  35 + * 短信渠道id
  36 + */
  37 + private Long channelId;
  38 +
  39 + /**
  40 + * 模板id
  41 + */
  42 + private String templateCode;
  43 +
  44 + /**
  45 + * 手机号
  46 + */
  47 + private String phone;
  48 +
  49 + /**
  50 + * 备注
  51 + */
  52 + private String remark;
  53 +
  54 + /**
  55 + * 发送状态
  56 + *
  57 + * @see SmsSendStatusEnum
  58 + */
  59 + private Integer sendStatus;
  60 +
  61 + /**
  62 + * 发送时间
  63 + */
  64 + private Date sendTime;
  65 +
  66 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SmsSendStatusEnum.java
... ... @@ -13,20 +13,26 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum SmsSendStatusEnum {
15 15  
  16 + //请求发送结果时失败
  17 + QUERY_SEND_FAIL(-3),
  18 +
  19 + //短信发送失败
  20 + SEND_FAIL(-2),
  21 +
  22 + //短信请求失败
  23 + QUERY_FAIL(-1),
  24 +
16 25 //异步转发中
17   - ASYNC(1),
  26 + ASYNC(0),
18 27  
19   - //发送中
20   - SENDING(2),
  28 + //请求成功
  29 + QUERY_SUCCESS(1),
21 30  
22   - //失败
23   - FAIL(3),
  31 + //短信成功
  32 + SEND_SUCCESS(2),
24 33  
25 34 //等待回执
26   - WAITING(4),
27   -
28   - //成功
29   - SUCCESS(5);
  35 + WAITING(3);
30 36  
31 37 private final int status;
32 38  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java
1 1 package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
5 6 import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
6 7 import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage;
7   -import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  8 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  9 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
8 10 import lombok.extern.slf4j.Slf4j;
9 11 import org.springframework.stereotype.Component;
10 12  
... ... @@ -20,12 +22,20 @@ import javax.annotation.Resource;
20 22 public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> {
21 23  
22 24 @Resource
23   - private SysSmsService sysSmsService;
  25 + private SysSmsChannelService smsChannelService;
  26 +
  27 + @Resource
  28 + private SysSmsQueryLogService smsQueryLogService;
24 29  
25 30 @Override
26 31 public void onMessage(SmsSendMessage message) {
27   - log.info("[onMessage][收到 发送短信 消息], content: " + message.toString());
28   - SmsResult send = sysSmsService.send(message.getSmsBody(), message.getTargetPhones());
  32 + log.info("[onMessage][收到 发送短信 消息], content: " + message.toString());
  33 + AbstractSmsClient smsClient = smsChannelService.getSmsClient(message.getSmsBody().getTemplateCode());
  34 + String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(message.getSmsBody().getTemplateCode());
  35 +
  36 + SmsResult result = smsClient.send(templateApiId, message.getSmsBody(), message.getTargetPhones());
  37 +
  38 + smsQueryLogService.afterSendLog(message.getSmsBody().getSmsLogId(), result);
29 39 }
30 40  
31 41 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsLogService.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java
... ... @@ -7,26 +7,25 @@ import cn.iocoder.dashboard.framework.sms.core.SmsResult;
7 7 import java.util.List;
8 8  
9 9 /**
10   - * 短信渠道Service接口
  10 + * 短信请求日志服务接口
11 11 *
12 12 * @author zzf
13 13 * @date 2021/1/25 9:24
14 14 */
15   -public interface SysSmsLogService {
  15 +public interface SysSmsQueryLogService {
16 16 /**
17 17 * 发送短信前的日志处理
18 18 *
19 19 * @param smsBody 短信内容
20 20 * @param targetPhones 发送对象手机号集合
21 21 * @param client 短信客户端
22   - * @param isAsync 是否异步发送
23 22 * @return 生成的日志id
24 23 */
25 24 // TODO FROM 芋艿 to ZZF: async 是针对发送的方式,对于日志不一定需要关心。这样,短信日志,实际就发送前插入,发送后更新结果.
26 25 // 这里只用于记录状态,毕竟异步可能推送失败,此时日志可记录该状态。
27 26  
28 27 // TODO FROM 芋艿 to ZZF:短信日志,群发的情况,应该是每个手机一条哈。虽然是群发,但是可能部分成功,部分失败;对应到短信平台,实际也是多条。
29   - Long beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client, Boolean isAsync);
  28 + void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client);
30 29  
31 30 /**
32 31 * 发送消息后的日志处理
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsSendLogService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +/**
  4 + * 短信发送日志服务接口
  5 + *
  6 + * @author zzf
  7 + * @date 13:48 2021/3/2
  8 + */
  9 +public interface SysSmsSendLogService {
  10 +
  11 + void getAndSaveSmsSendLog();
  12 +
  13 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java
1 1 package cn.iocoder.dashboard.modules.system.service.sms;
2 2  
3 3 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
4   -import cn.iocoder.dashboard.framework.sms.core.SmsResult;
5   -import org.apache.commons.lang3.StringUtils;
6 4  
  5 +import javax.servlet.ServletRequest;
7 6 import java.util.Arrays;
8 7 import java.util.Collections;
9 8 import java.util.List;
10 9  
11 10 /**
12 11 * 短信Service接口
  12 + * 只支持异步,因此没有返回值
13 13 *
14 14 * @author zzf
15 15 * @date 2021/1/25 9:24
... ... @@ -21,23 +21,17 @@ public interface SysSmsService {
21 21 *
22 22 * @param smsBody 消息内容
23 23 * @param targetPhones 发送对象手机号列表
24   - * @return 是否发送成功
25 24 */
26   - SmsResult send(SmsBody smsBody, List<String> targetPhones);
  25 + void send(SmsBody smsBody, List<String> targetPhones);
27 26  
28 27 /**
29 28 * 发送消息
30 29 *
31 30 * @param smsBody 消息内容
32 31 * @param targetPhone 发送对象手机号
33   - * @return 是否发送成功
34 32 */
35   - default SmsResult send(SmsBody smsBody, String targetPhone) {
36   - if (StringUtils.isBlank(targetPhone)) {
37   - return failResult("targetPhone must not null.");
38   - }
39   -
40   - return send(smsBody, Collections.singletonList(targetPhone));
  33 + default void send(SmsBody smsBody, String targetPhone) {
  34 + send(smsBody, Collections.singletonList(targetPhone));
41 35 }
42 36  
43 37 /**
... ... @@ -45,57 +39,16 @@ public interface SysSmsService {
45 39 *
46 40 * @param smsBody 消息内容
47 41 * @param targetPhones 发送对象手机号数组
48   - * @return 是否发送成功
49 42 */
50   - default SmsResult send(SmsBody smsBody, String... targetPhones) {
51   - if (targetPhones == null) {
52   - return failResult("targetPhones must not null.");
53   - }
54   -
55   - return send(smsBody, Arrays.asList(targetPhones));
  43 + default void send(SmsBody smsBody, String... targetPhones) {
  44 + send(smsBody, Arrays.asList(targetPhones));
56 45 }
57 46  
58   -
59 47 /**
60   - * 异步发送消息
  48 + * 处理短信发送回调函数
61 49 *
62   - * @param msgBody 消息内容
63   - * @param targetPhones 发送对象列表
  50 + * @param request 请求
  51 + * @return 响应数据
64 52 */
65   - void sendAsync(SmsBody msgBody, List<String> targetPhones);
66   -
67   - /**
68   - * 异步发送消息
69   - *
70   - * @param msgBody 消息内容
71   - * @param targetPhone 发送对象
72   - */
73   - default void sendAsync(SmsBody msgBody, String targetPhone) {
74   - if (StringUtils.isBlank(targetPhone)) {
75   - return;
76   - }
77   - sendAsync(msgBody, Collections.singletonList(targetPhone));
78   - }
79   -
80   - /**
81   - * 异步发送消息
82   - *
83   - * @param msgBody 消息内容
84   - * @param targetPhones 发送对象列表
85   - */
86   - default void sendAsync(SmsBody msgBody, String... targetPhones) {
87   - if (targetPhones == null) {
88   - return;
89   - }
90   - sendAsync(msgBody, Arrays.asList(targetPhones));
91   - }
92   -
93   -
94   - default SmsResult failResult(String message) {
95   - SmsResult resultBody = new SmsResult();
96   - resultBody.setSuccess(false);
97   - resultBody.setMessage(message);
98   - return resultBody;
99   - }
100   -
  53 + Object smsSendCallbackHandle(ServletRequest request);
101 54 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsLogServiceImpl.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsQueryLogServiceImpl.java
... ... @@ -4,10 +4,10 @@ import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
6 6 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
7   -import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsLogMapper;
8   -import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsLogDO;
  7 +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
  8 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
9 9 import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
10   -import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
  10 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
11 11 import cn.iocoder.dashboard.util.json.JsonUtils;
12 12 import org.springframework.stereotype.Service;
13 13  
... ... @@ -15,61 +15,43 @@ import javax.annotation.Resource;
15 15 import java.util.List;
16 16  
17 17 /**
18   - * 短信日志Service实现类
  18 + * 短信请求日志服务实现类
19 19 *
20 20 * @author zzf
21   - * @date 2021/1/25 9:25
  21 + * @date 13:50 2021/3/2
22 22 */
23 23 @Service
24   -public class SysSmsLogServiceImpl implements SysSmsLogService {
  24 +public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
25 25  
26 26 @Resource
27   - private SysSmsLogMapper logMapper;
  27 + private SysSmsQueryLogMapper logMapper;
28 28  
29 29 @Override
30   - public Long beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client, Boolean isAsync) {
31   - SysSmsLogDO smsLog = new SysSmsLogDO();
32   - if (smsBody.getSmsLogId() != null) {
33   - smsLog.setId(smsBody.getSmsLogId());
34   - smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus());
35   - logMapper.updateById(smsLog);
36   - return smsBody.getSmsLogId();
37   - } else {
38   - SmsChannelProperty property = client.getProperty();
39   -
40   - smsLog.setChannelCode(property.getCode())
41   - .setChannelId(property.getId())
42   - .setTemplateCode(smsBody.getTemplateCode())
43   - .setPhones(JsonUtils.toJsonString(targetPhones))
44   - .setContent(smsBody.getParams().toString());
45   -
46   - if (isAsync) {
47   - smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus());
48   - } else {
49   - smsLog.setSendStatus(SmsSendStatusEnum.SENDING.getStatus());
50   - }
51   - logMapper.insert(smsLog);
52   - return smsLog.getId();
53   - }
  30 + public void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client) {
  31 + SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
  32 + SmsChannelProperty property = client.getProperty();
  33 +
  34 + smsLog.setChannelCode(property.getCode())
  35 + .setChannelId(property.getId())
  36 + .setTemplateCode(smsBody.getTemplateCode())
  37 + .setPhones(targetPhones)
  38 + .setContent(smsBody.getParams().toString());
  39 +
  40 + smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus());
  41 + logMapper.insert(smsLog);
  42 + smsBody.setSmsLogId(smsLog.getId());
54 43 }
55 44  
56 45 @Override
57 46 public void afterSendLog(Long logId, SmsResult result) {
58   - SysSmsLogDO smsLog = new SysSmsLogDO();
  47 + SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
59 48 smsLog.setId(logId);
60 49 if (result.getSuccess()) {
61   - smsLog.setSendStatus(SmsSendStatusEnum.SUCCESS.getStatus());
62   - SysSmsLogDO smsLogDO = logMapper.selectById(logId);
63   - result.getResult().forEach(s -> {
64   - smsLogDO.setPhones(s.getPhone());
65   - smsLogDO.setSendStatus(s.getStatus());
66   - smsLogDO.setRemark(s.getMessage());
67   - smsLogDO.setCreateTime(s.getCreateTime());
68   - logMapper.insert(smsLogDO);
69   - });
  50 + smsLog.setSendStatus(SmsSendStatusEnum.QUERY_SUCCESS.getStatus());
  51 + smsLog.setSendResultParam(result.getSendResultParam());
70 52 } else {
71   - smsLog.setSendStatus(SmsSendStatusEnum.FAIL.getStatus());
72   - smsLog.setRemark(result.getMessage() + JsonUtils.toJsonString(result.getResult()));
  53 + smsLog.setSendStatus(SmsSendStatusEnum.QUERY_FAIL.getStatus());
  54 + smsLog.setRemark(result.getMessage());
73 55 }
74 56 logMapper.updateById(smsLog);
75 57 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsSendLogServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms.impl;
  2 +
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
  5 +import cn.iocoder.dashboard.framework.sms.client.NeedQuerySendResultSmsClient;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  7 +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
  8 +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsSendLogMapper;
  9 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
  10 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsSendLogDO;
  11 +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
  12 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  13 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.scheduling.annotation.Scheduled;
  16 +import org.springframework.stereotype.Service;
  17 +
  18 +import javax.annotation.Resource;
  19 +import java.util.List;
  20 +
  21 +/**
  22 + * 短信发送日志服务实现类
  23 + *
  24 + * @author zzf
  25 + * @date 2021/1/25 9:25
  26 + */
  27 +@Slf4j
  28 +@Service
  29 +public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
  30 +
  31 + @Resource
  32 + private SysSmsQueryLogMapper smsQueryLogMapper;
  33 +
  34 + @Resource
  35 + private SysSmsSendLogMapper smsSendLogMapper;
  36 +
  37 + @Resource
  38 + private SysSmsChannelService smsChannelService;
  39 +
  40 + /**
  41 + * 定时执行 {@link #getSmsSendResultJob()} 的周期
  42 + */
  43 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  44 +
  45 +
  46 + @Override
  47 + public void getAndSaveSmsSendLog() {
  48 +
  49 + List<SysSmsQueryLogDO> noResultQueryLogList = smsQueryLogMapper.selectNoResultQueryLogList();
  50 +
  51 + if (CollectionUtil.isEmpty(noResultQueryLogList)) {
  52 + return;
  53 + }
  54 + //用于添加的发送日志对象
  55 + SysSmsSendLogDO insertSendLog = new SysSmsSendLogDO();
  56 + //用于修改状态的请求日志对象
  57 + SysSmsQueryLogDO updateQueryLog = new SysSmsQueryLogDO();
  58 +
  59 + noResultQueryLogList.forEach(queryLog -> {
  60 + AbstractSmsClient smsClient = smsChannelService.getSmsClient(queryLog.getTemplateCode());
  61 +
  62 + updateQueryLog.setId(queryLog.getId());
  63 +
  64 + // 只处理实现了获取发送结果方法的短信客户端,理论上这里都是满足条件的,以防万一加个判断。
  65 + if (smsClient instanceof NeedQuerySendResultSmsClient) {
  66 + //初始化点字段值
  67 + queryLog2SendLong(insertSendLog, queryLog);
  68 +
  69 + NeedQuerySendResultSmsClient querySendResultSmsClient = (NeedQuerySendResultSmsClient) smsClient;
  70 + try {
  71 + List<SmsResultDetail> smsSendResult = querySendResultSmsClient.getSmsSendResult(queryLog.getRemark());
  72 + smsSendResult.forEach(resultDetail -> {
  73 + insertSendLog.setPhone(resultDetail.getPhone());
  74 + insertSendLog.setSendStatus(resultDetail.getSendStatus());
  75 + insertSendLog.setSendTime(resultDetail.getSendTime());
  76 + insertSendLog.setRemark(resultDetail.getMessage());
  77 + smsSendLogMapper.insert(insertSendLog);
  78 + });
  79 + } catch (Exception e) {
  80 + //exception handle
  81 + log.error("query send result fail, exception: " + e.getMessage());
  82 +
  83 + updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
  84 + updateQueryLog.setRemark(e.getMessage());
  85 + smsQueryLogMapper.updateById(updateQueryLog);
  86 + return;
  87 + }
  88 + } else {
  89 + //理论上这里都是满足条件的,以防万一加个判断。
  90 + updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
  91 + smsQueryLogMapper.updateById(updateQueryLog);
  92 + }
  93 + updateQueryLog.setSendStatus(SmsSendStatusEnum.SEND_SUCCESS.getStatus());
  94 + updateQueryLog.setRemark(String.format("日志(id = %s)对应的客户端没有继承NeedQuerySendResultSmsClient, 不能获取短信结果。", queryLog.getId()));
  95 + smsQueryLogMapper.updateById(updateQueryLog);
  96 + });
  97 + }
  98 +
  99 + private void queryLog2SendLong(SysSmsSendLogDO insertSendLog, SysSmsQueryLogDO queryLog) {
  100 + insertSendLog.setChannelCode(queryLog.getChannelCode());
  101 + insertSendLog.setChannelId(queryLog.getChannelId());
  102 + insertSendLog.setTemplateCode(queryLog.getTemplateCode());
  103 + }
  104 +
  105 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  106 + public void getSmsSendResultJob() {
  107 + getAndSaveSmsSendLog();
  108 + }
  109 +}
... ...
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;
2 2  
3 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5   -import cn.iocoder.dashboard.framework.sms.core.SmsResult;
6 5 import cn.iocoder.dashboard.modules.system.mq.producer.sms.SmsProducer;
7 6 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
8   -import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
  7 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
9 8 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  9 +import org.springframework.scheduling.annotation.Scheduled;
10 10 import org.springframework.stereotype.Service;
11 11  
12 12 import javax.annotation.Resource;
... ... @@ -25,30 +25,18 @@ public class SysSmsServiceImpl implements SysSmsService {
25 25 private SysSmsChannelService channelService;
26 26  
27 27 @Resource
28   - private SysSmsLogService logService;
  28 + private SysSmsQueryLogService logService;
29 29  
30 30 @Resource
31 31 private SmsProducer smsProducer;
32 32  
33 33 @Override
34   - public SmsResult send(SmsBody smsBody, List<String> targetPhones) {
  34 + public void send(SmsBody smsBody, List<String> targetPhones) {
35 35 AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode());
36   - String templateApiId = channelService.getSmsTemplateApiIdByCode(smsBody.getTemplateCode());
37   - Long logId = logService.beforeSendLog(smsBody, targetPhones, client, false);
38   -
39   - SmsResult result = client.send(templateApiId, smsBody, targetPhones);
40   -
41   - logService.afterSendLog(logId, result);
42   -
43   - return result;
44   - }
45   -
46   - // TODO FROM 芋艿 to ZZF:可能要讨论下,对于短信发送来说,貌似只提供异步发送即可。对于业务来说,一定不能依赖短信的发送结果。
47   - // 我的想法是1、很多短信,比如验证码,总还是需要知道是否发送成功的。2、别人可以不用,我们不能没有。3、实现挺简单的,个人觉得无需纠结。
48   - @Override
49   - public void sendAsync(SmsBody smsBody, List<String> targetPhones) {
50   - AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode());
51   - logService.beforeSendLog(smsBody, targetPhones, client, true);
  36 + logService.beforeSendLog(smsBody, targetPhones, client);
52 37 smsProducer.sendSmsSendMessage(smsBody, targetPhones);
53 38 }
  39 +
  40 + // TODO FROM 芋艿 to ZZF:可能要讨论下,对于短信发送来说,貌似只提供异步发送即可。对于业务来说,一定不能依赖短信的发送结果.
  41 +
54 42 }
... ...
src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
... ... @@ -20,7 +20,7 @@ public class JsonUtils {
20 20  
21 21 /**
22 22 * 初始化 objectMapper 属性
23   - *
  23 + * <p>
24 24 * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
25 25 *
26 26 * @param objectMapper ObjectMapper 对象
... ... @@ -67,4 +67,12 @@ public class JsonUtils {
67 67 }
68 68 }
69 69  
  70 + public static <T> T parseByType(String text, TypeReference<T> typeReference) {
  71 + try {
  72 + return objectMapper.readValue(text, typeReference);
  73 + } catch (IOException e) {
  74 + throw new RuntimeException(e);
  75 + }
  76 + }
  77 +
70 78 }
... ...