Commit 767cd90279e2247e704156f7c028b0b97560f663

Authored by zengzefeng
1 parent c3372d4b

批量发送半成品

Showing 29 changed files with 693 additions and 188 deletions
@@ -184,12 +184,6 @@ @@ -184,12 +184,6 @@
184 </dependency> 184 </dependency>
185 185
186 <dependency> 186 <dependency>
187 - <groupId>org.projectlombok</groupId>  
188 - <artifactId>lombok</artifactId>  
189 - <version>${lombok.version}</version>  
190 - </dependency>  
191 -  
192 - <dependency>  
193 <groupId>org.mapstruct</groupId> 187 <groupId>org.mapstruct</groupId>
194 <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher --> 188 <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
195 <version>${mapstruct.version}</version> 189 <version>${mapstruct.version}</version>
@@ -222,17 +216,25 @@ @@ -222,17 +216,25 @@
222 <version>${easyexcel.verion}</version> 216 <version>${easyexcel.verion}</version>
223 </dependency> 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 <dependency> 227 <dependency>
226 <groupId>com.aliyun</groupId> 228 <groupId>com.aliyun</groupId>
227 <artifactId>aliyun-java-sdk-core</artifactId> 229 <artifactId>aliyun-java-sdk-core</artifactId>
228 <version>4.5.18</version> 230 <version>4.5.18</version>
229 </dependency> 231 </dependency>
230 -  
231 <dependency> 232 <dependency>
232 <groupId>com.aliyun</groupId> 233 <groupId>com.aliyun</groupId>
233 <artifactId>aliyun-java-sdk-dysmsapi</artifactId> 234 <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
234 <version>2.1.0</version> 235 <version>2.1.0</version>
235 </dependency> 236 </dependency>
  237 + <!-- SMS SDK end -->
236 238
237 </dependencies> 239 </dependencies>
238 240
sql/sms.sql
@@ -12,6 +12,8 @@ CREATE TABLE `sms_channel` @@ -12,6 +12,8 @@ CREATE TABLE `sms_channel`
12 `code` varchar(50) NOT NULL COMMENT '编码(来自枚举类 阿里、华为、七牛等)', 12 `code` varchar(50) NOT NULL COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
13 `api_key` varchar(100) NOT NULL COMMENT '账号id', 13 `api_key` varchar(100) NOT NULL COMMENT '账号id',
14 `api_secret` varchar(100) NOT NULL COMMENT '账号秘钥', 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 `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识', 17 `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
16 `name` varchar(50) NOT NULL COMMENT '名称', 18 `name` varchar(50) NOT NULL COMMENT '名称',
17 `signature` varchar(50) NOT NULL COMMENT '签名值', 19 `signature` varchar(50) NOT NULL COMMENT '签名值',
@@ -61,22 +63,46 @@ CREATE TABLE `sms_template` @@ -61,22 +63,46 @@ CREATE TABLE `sms_template`
61 DEFAULT CHARSET = utf8mb4 COMMENT ='短信模板'; 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 -- Table structure for sms_log 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 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号', 95 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
70 `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)', 96 `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)',
71 `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id', 97 `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id',
72 `template_code` varchar(50) NOT NULL COMMENT '渠道编码', 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 `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容', 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 PRIMARY KEY (`id`) USING BTREE 105 PRIMARY KEY (`id`) USING BTREE
80 ) ENGINE = InnoDB 106 ) ENGINE = InnoDB
81 AUTO_INCREMENT = 1 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,7 +58,7 @@ public abstract class AbstractSmsClient implements SmsClient {
58 * @return 短信发送结果 58 * @return 短信发送结果
59 * @throws Exception 调用发送失败,抛出异常 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 protected void beforeSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception { 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,7 +2,6 @@ package cn.iocoder.dashboard.framework.sms.client;
2 2
3 import cn.hutool.core.date.DateUtil; 3 import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
5 -import cn.hutool.core.util.StrUtil;  
6 import cn.iocoder.dashboard.framework.sms.core.SmsBody; 5 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
7 import cn.iocoder.dashboard.framework.sms.core.SmsResult; 6 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
8 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail; 7 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
@@ -14,11 +13,11 @@ import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest; @@ -14,11 +13,11 @@ import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
14 import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse; 13 import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
15 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; 14 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
16 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; 15 import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
  16 +import com.aliyuncs.exceptions.ClientException;
17 import com.aliyuncs.http.MethodType; 17 import com.aliyuncs.http.MethodType;
18 import com.aliyuncs.profile.DefaultProfile; 18 import com.aliyuncs.profile.DefaultProfile;
19 import com.aliyuncs.profile.IClientProfile; 19 import com.aliyuncs.profile.IClientProfile;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 -import org.apache.commons.lang3.StringUtils;  
22 21
23 import java.util.ArrayList; 22 import java.util.ArrayList;
24 import java.util.Collection; 23 import java.util.Collection;
@@ -31,7 +30,7 @@ import java.util.List; @@ -31,7 +30,7 @@ import java.util.List;
31 * @date 2021/1/25 14:17 30 * @date 2021/1/25 14:17
32 */ 31 */
33 @Slf4j 32 @Slf4j
34 -public class AliyunSmsClient extends AbstractSmsClient { 33 +public class AliyunSmsClient extends AbstractSmsClient implements NeedQuerySendResultSmsClient {
35 34
36 private static final String OK = "OK"; 35 private static final String OK = "OK";
37 36
@@ -70,35 +69,43 @@ public class AliyunSmsClient extends AbstractSmsClient { @@ -70,35 +69,43 @@ public class AliyunSmsClient extends AbstractSmsClient {
70 request.setTemplateParam(smsBody.getParamsStr()); 69 request.setTemplateParam(smsBody.getParamsStr());
71 SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); 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 log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage()); 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 QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest(); 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 QuerySendDetailsResponse acsResponse = acsClient.getAcsResponse(querySendDetailsRequest); 90 QuerySendDetailsResponse acsResponse = acsClient.getAcsResponse(querySendDetailsRequest);
83 List<SmsResultDetail> resultDetailList = new ArrayList<>(Integer.parseInt(acsResponse.getTotalCount())); 91 List<SmsResultDetail> resultDetailList = new ArrayList<>(Integer.parseInt(acsResponse.getTotalCount()));
84 acsResponse.getSmsSendDetailDTOs().forEach(s -> { 92 acsResponse.getSmsSendDetailDTOs().forEach(s -> {
85 SmsResultDetail resultDetail = new SmsResultDetail(); 93 SmsResultDetail resultDetail = new SmsResultDetail();
86 - resultDetail.setCreateTime(DateUtil.parseDateTime(s.getSendDate())); 94 + resultDetail.setSendTime(DateUtil.parseDateTime(s.getSendDate()));
87 resultDetail.setMessage(s.getContent()); 95 resultDetail.setMessage(s.getContent());
88 resultDetail.setPhone(s.getPhoneNum()); 96 resultDetail.setPhone(s.getPhoneNum());
89 - resultDetail.setStatus(statusConvert(s.getSendStatus())); 97 + resultDetail.setSendStatus(statusConvert(s.getSendStatus()));
90 resultDetailList.add(resultDetail); 98 resultDetailList.add(resultDetail);
91 }); 99 });
92 - resultBody.setResult(resultDetailList);  
93 - return resultBody; 100 + return resultDetailList;
94 } 101 }
95 102
96 private int statusConvert(Long aliSendStatus) { 103 private int statusConvert(Long aliSendStatus) {
97 if (aliSendStatus == 1L) { 104 if (aliSendStatus == 1L) {
98 - return SmsSendStatusEnum.SUCCESS.getStatus(); 105 + return SmsSendStatusEnum.SEND_SUCCESS.getStatus();
99 } 106 }
100 if (aliSendStatus == 2L) { 107 if (aliSendStatus == 2L) {
101 - return SmsSendStatusEnum.FAIL.getStatus(); 108 + return SmsSendStatusEnum.SEND_FAIL.getStatus();
102 } 109 }
103 return SmsSendStatusEnum.WAITING.getStatus(); 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,8 +2,10 @@ package cn.iocoder.dashboard.framework.sms.client;
2 2
3 import cn.iocoder.dashboard.framework.sms.core.SmsBody; 3 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
4 import cn.iocoder.dashboard.framework.sms.core.SmsResult; 4 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
5 6
6 import java.util.Collection; 7 import java.util.Collection;
  8 +import java.util.List;
7 9
8 /** 10 /**
9 * 短信父接口 11 * 短信父接口
@@ -23,4 +25,7 @@ public interface SmsClient { @@ -23,4 +25,7 @@ public interface SmsClient {
23 */ 25 */
24 SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets); 26 SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets);
25 27
  28 +
  29 + //List<SmsResultDetail> getSmsSendResult(String jsonObjectParam);
  30 +
26 } 31 }
27 \ No newline at end of file 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,7 +4,6 @@ import cn.iocoder.dashboard.util.json.JsonUtils;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import java.util.Map; 6 import java.util.Map;
7 -import java.util.UUID;  
8 7
9 /** 8 /**
10 * 消息内容实体类 9 * 消息内容实体类
@@ -23,6 +22,11 @@ public class SmsBody { @@ -23,6 +22,11 @@ public class SmsBody {
23 private String templateCode; 22 private String templateCode;
24 23
25 /** 24 /**
  25 + * 模板编码
  26 + */
  27 + private String templateContent;
  28 +
  29 + /**
26 * 参数列表 30 * 参数列表
27 */ 31 */
28 private Map<String, String> params; 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 package cn.iocoder.dashboard.framework.sms.core; 1 package cn.iocoder.dashboard.framework.sms.core;
2 2
3 import lombok.Data; 3 import lombok.Data;
  4 +import lombok.experimental.Accessors;
4 5
5 import java.io.Serializable; 6 import java.io.Serializable;
6 -import java.util.List;  
7 7
8 /** 8 /**
9 * 消息内容实体类 9 * 消息内容实体类
10 */ 10 */
11 @Data 11 @Data
  12 +@Accessors(chain = true)
12 public class SmsResult implements Serializable { 13 public class SmsResult implements Serializable {
13 14
14 /** 15 /**
@@ -17,6 +18,11 @@ public class SmsResult implements Serializable { @@ -17,6 +18,11 @@ public class SmsResult implements Serializable {
17 private Boolean success; 18 private Boolean success;
18 19
19 /** 20 /**
  21 + * 第三方唯一标识
  22 + */
  23 + private String apiId;
  24 +
  25 + /**
20 * 状态码 26 * 状态码
21 */ 27 */
22 private String code; 28 private String code;
@@ -27,10 +33,9 @@ public class SmsResult implements Serializable { @@ -27,10 +33,9 @@ public class SmsResult implements Serializable {
27 private String message; 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 public static SmsResult failResult(String message) { 40 public static SmsResult failResult(String message) {
36 SmsResult resultBody = new SmsResult(); 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,7 +14,7 @@ public class SmsResultDetail implements Serializable {
14 /** 14 /**
15 * 短信发送状态 {@link cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum} 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,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,4 +54,15 @@ public class SmsChannelProperty implements Serializable {
54 @NotEmpty(message = "签名值不能为空") 54 @NotEmpty(message = "签名值不能为空")
55 private String signature; 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 package cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms; 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 import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
6 6
7 @Mapper 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,6 +28,16 @@ public class SysSmsChannelDO extends BaseDO {
28 private String code; 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 * 渠道账号id 41 * 渠道账号id
32 */ 42 */
33 private String apiKey; 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 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms; 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
2 2
  3 +import com.baomidou.mybatisplus.annotation.TableField;
3 import com.baomidou.mybatisplus.annotation.TableName; 4 import com.baomidou.mybatisplus.annotation.TableName;
  5 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
4 import lombok.Data; 6 import lombok.Data;
5 import lombok.EqualsAndHashCode; 7 import lombok.EqualsAndHashCode;
6 import lombok.experimental.Accessors; 8 import lombok.experimental.Accessors;
7 9
8 import java.io.Serializable; 10 import java.io.Serializable;
9 import java.util.Date; 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,8 +20,8 @@ import java.util.Date;
17 @Data 20 @Data
18 @EqualsAndHashCode 21 @EqualsAndHashCode
19 @Accessors(chain = true) 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,9 +44,10 @@ public class SysSmsLogDO implements Serializable {
41 private String templateCode; 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,17 +55,34 @@ public class SysSmsLogDO implements Serializable {
51 private String content; 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 private String createBy; 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,20 +13,26 @@ import lombok.Getter;
13 @AllArgsConstructor 13 @AllArgsConstructor
14 public enum SmsSendStatusEnum { 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 private final int status; 37 private final int status;
32 38
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java
1 package cn.iocoder.dashboard.modules.system.mq.consumer.sms; 1 package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
2 2
3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 import cn.iocoder.dashboard.framework.sms.core.SmsResult; 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
5 import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage; 6 import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
6 import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage; 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 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
9 import org.springframework.stereotype.Component; 11 import org.springframework.stereotype.Component;
10 12
@@ -20,12 +22,20 @@ import javax.annotation.Resource; @@ -20,12 +22,20 @@ import javax.annotation.Resource;
20 public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> { 22 public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> {
21 23
22 @Resource 24 @Resource
23 - private SysSmsService sysSmsService; 25 + private SysSmsChannelService smsChannelService;
  26 +
  27 + @Resource
  28 + private SysSmsQueryLogService smsQueryLogService;
24 29
25 @Override 30 @Override
26 public void onMessage(SmsSendMessage message) { 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,26 +7,25 @@ import cn.iocoder.dashboard.framework.sms.core.SmsResult;
7 import java.util.List; 7 import java.util.List;
8 8
9 /** 9 /**
10 - * 短信渠道Service接口 10 + * 短信请求日志服务接口
11 * 11 *
12 * @author zzf 12 * @author zzf
13 * @date 2021/1/25 9:24 13 * @date 2021/1/25 9:24
14 */ 14 */
15 -public interface SysSmsLogService { 15 +public interface SysSmsQueryLogService {
16 /** 16 /**
17 * 发送短信前的日志处理 17 * 发送短信前的日志处理
18 * 18 *
19 * @param smsBody 短信内容 19 * @param smsBody 短信内容
20 * @param targetPhones 发送对象手机号集合 20 * @param targetPhones 发送对象手机号集合
21 * @param client 短信客户端 21 * @param client 短信客户端
22 - * @param isAsync 是否异步发送  
23 * @return 生成的日志id 22 * @return 生成的日志id
24 */ 23 */
25 // TODO FROM 芋艿 to ZZF: async 是针对发送的方式,对于日志不一定需要关心。这样,短信日志,实际就发送前插入,发送后更新结果. 24 // TODO FROM 芋艿 to ZZF: async 是针对发送的方式,对于日志不一定需要关心。这样,短信日志,实际就发送前插入,发送后更新结果.
26 // 这里只用于记录状态,毕竟异步可能推送失败,此时日志可记录该状态。 25 // 这里只用于记录状态,毕竟异步可能推送失败,此时日志可记录该状态。
27 26
28 // TODO FROM 芋艿 to ZZF:短信日志,群发的情况,应该是每个手机一条哈。虽然是群发,但是可能部分成功,部分失败;对应到短信平台,实际也是多条。 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 package cn.iocoder.dashboard.modules.system.service.sms; 1 package cn.iocoder.dashboard.modules.system.service.sms;
2 2
3 import cn.iocoder.dashboard.framework.sms.core.SmsBody; 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 import java.util.Arrays; 6 import java.util.Arrays;
8 import java.util.Collections; 7 import java.util.Collections;
9 import java.util.List; 8 import java.util.List;
10 9
11 /** 10 /**
12 * 短信Service接口 11 * 短信Service接口
  12 + * 只支持异步,因此没有返回值
13 * 13 *
14 * @author zzf 14 * @author zzf
15 * @date 2021/1/25 9:24 15 * @date 2021/1/25 9:24
@@ -21,23 +21,17 @@ public interface SysSmsService { @@ -21,23 +21,17 @@ public interface SysSmsService {
21 * 21 *
22 * @param smsBody 消息内容 22 * @param smsBody 消息内容
23 * @param targetPhones 发送对象手机号列表 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 * @param smsBody 消息内容 30 * @param smsBody 消息内容
32 * @param targetPhone 发送对象手机号 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,57 +39,16 @@ public interface SysSmsService {
45 * 39 *
46 * @param smsBody 消息内容 40 * @param smsBody 消息内容
47 * @param targetPhones 发送对象手机号数组 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,10 +4,10 @@ import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 import cn.iocoder.dashboard.framework.sms.core.SmsBody; 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 import cn.iocoder.dashboard.framework.sms.core.SmsResult; 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
6 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty; 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 import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum; 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 import cn.iocoder.dashboard.util.json.JsonUtils; 11 import cn.iocoder.dashboard.util.json.JsonUtils;
12 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
13 13
@@ -15,61 +15,43 @@ import javax.annotation.Resource; @@ -15,61 +15,43 @@ import javax.annotation.Resource;
15 import java.util.List; 15 import java.util.List;
16 16
17 /** 17 /**
18 - * 短信日志Service实现类 18 + * 短信请求日志服务实现类
19 * 19 *
20 * @author zzf 20 * @author zzf
21 - * @date 2021/1/25 9:25 21 + * @date 13:50 2021/3/2
22 */ 22 */
23 @Service 23 @Service
24 -public class SysSmsLogServiceImpl implements SysSmsLogService { 24 +public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
25 25
26 @Resource 26 @Resource
27 - private SysSmsLogMapper logMapper; 27 + private SysSmsQueryLogMapper logMapper;
28 28
29 @Override 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 @Override 45 @Override
57 public void afterSendLog(Long logId, SmsResult result) { 46 public void afterSendLog(Long logId, SmsResult result) {
58 - SysSmsLogDO smsLog = new SysSmsLogDO(); 47 + SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
59 smsLog.setId(logId); 48 smsLog.setId(logId);
60 if (result.getSuccess()) { 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 } else { 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 logMapper.updateById(smsLog); 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,11 +2,11 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl;
2 2
3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient; 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 import cn.iocoder.dashboard.framework.sms.core.SmsBody; 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 -import cn.iocoder.dashboard.framework.sms.core.SmsResult;  
6 import cn.iocoder.dashboard.modules.system.mq.producer.sms.SmsProducer; 5 import cn.iocoder.dashboard.modules.system.mq.producer.sms.SmsProducer;
7 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService; 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 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService; 8 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  9 +import org.springframework.scheduling.annotation.Scheduled;
10 import org.springframework.stereotype.Service; 10 import org.springframework.stereotype.Service;
11 11
12 import javax.annotation.Resource; 12 import javax.annotation.Resource;
@@ -25,30 +25,18 @@ public class SysSmsServiceImpl implements SysSmsService { @@ -25,30 +25,18 @@ public class SysSmsServiceImpl implements SysSmsService {
25 private SysSmsChannelService channelService; 25 private SysSmsChannelService channelService;
26 26
27 @Resource 27 @Resource
28 - private SysSmsLogService logService; 28 + private SysSmsQueryLogService logService;
29 29
30 @Resource 30 @Resource
31 private SmsProducer smsProducer; 31 private SmsProducer smsProducer;
32 32
33 @Override 33 @Override
34 - public SmsResult send(SmsBody smsBody, List<String> targetPhones) { 34 + public void send(SmsBody smsBody, List<String> targetPhones) {
35 AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode()); 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 smsProducer.sendSmsSendMessage(smsBody, targetPhones); 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,7 +20,7 @@ public class JsonUtils {
20 20
21 /** 21 /**
22 * 初始化 objectMapper 属性 22 * 初始化 objectMapper 属性
23 - * 23 + * <p>
24 * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean 24 * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
25 * 25 *
26 * @param objectMapper ObjectMapper 对象 26 * @param objectMapper ObjectMapper 对象
@@ -67,4 +67,12 @@ public class JsonUtils { @@ -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 }