批量发送半成品

This commit is contained in:
zengzefeng
2021-03-08 09:15:52 +08:00
parent c3372d4bd2
commit 767cd90279
30 changed files with 735 additions and 230 deletions

View File

@@ -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 {
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
/**
* 参数列表
*/

View File

@@ -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";
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;
}