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