sms:移除 SmsCodeMapping + SmsCommonResult,简化短信的封装
parent
4118f25d75
commit
562f82580e
|
@ -31,8 +31,8 @@ public interface SmsClient {
|
|||
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
|
||||
* @return 短信发送结果
|
||||
*/
|
||||
SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams);
|
||||
SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable;
|
||||
|
||||
/**
|
||||
* 解析接收短信的接收结果
|
||||
|
@ -49,6 +49,6 @@ public interface SmsClient {
|
|||
* @param apiTemplateId 短信 API 的模板编号
|
||||
* @return 短信模板
|
||||
*/
|
||||
SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId);
|
||||
SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 将 API 的错误码,转换为通用的错误码
|
||||
*
|
||||
* @see SmsCommonResult
|
||||
* @see SmsFrameworkErrorCodeConstants
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface SmsCodeMapping extends Function<String, ErrorCode> {
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 短信的 CommonResult 拓展类
|
||||
*
|
||||
* 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
|
||||
*
|
||||
* 另外,一些短信平台(例如说阿里云、腾讯云)会返回一个请求编号,用于排查请求失败的问题,我们设置到 {@link #apiRequestId} 字段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class SmsCommonResult<T> extends CommonResult<T> {
|
||||
|
||||
/**
|
||||
* API 返回错误码
|
||||
*
|
||||
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||
*/
|
||||
private String apiCode;
|
||||
/**
|
||||
* API 返回提示
|
||||
*/
|
||||
private String apiMsg;
|
||||
|
||||
/**
|
||||
* API 请求编号
|
||||
*/
|
||||
private String apiRequestId;
|
||||
|
||||
private SmsCommonResult() {
|
||||
}
|
||||
|
||||
public static <T> SmsCommonResult<T> build(String apiCode, String apiMsg, String apiRequestId,
|
||||
T data, SmsCodeMapping codeMapping) {
|
||||
Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
|
||||
SmsCommonResult<T> result = new SmsCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg).setApiRequestId(apiRequestId);
|
||||
result.setData(data);
|
||||
// 翻译错误码
|
||||
if (codeMapping != null) {
|
||||
ErrorCode errorCode = codeMapping.apply(apiCode);
|
||||
if (errorCode == null) {
|
||||
errorCode = SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> SmsCommonResult<T> error(Throwable ex) {
|
||||
SmsCommonResult<T> result = new SmsCommonResult<>();
|
||||
result.setCode(SmsFrameworkErrorCodeConstants.EXCEPTION.getCode());
|
||||
result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,9 +10,34 @@ import lombok.Data;
|
|||
@Data
|
||||
public class SmsSendRespDTO {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* API 请求编号
|
||||
*/
|
||||
private String apiRequestId;
|
||||
|
||||
// ==================== 成功时字段 ====================
|
||||
|
||||
/**
|
||||
* 短信 API 发送返回的序号
|
||||
*/
|
||||
private String serialNo;
|
||||
|
||||
// ==================== 失败时字段 ====================
|
||||
|
||||
/**
|
||||
* API 返回错误码
|
||||
*
|
||||
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||
*/
|
||||
private String apiCode;
|
||||
/**
|
||||
* API 返回提示
|
||||
*/
|
||||
private String apiMsg;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 短信客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
*
|
||||
|
@ -25,14 +17,9 @@ public abstract class AbstractSmsClient implements SmsClient {
|
|||
* 短信渠道配置
|
||||
*/
|
||||
protected volatile SmsChannelProperties properties;
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*/
|
||||
protected final SmsCodeMapping codeMapping;
|
||||
|
||||
public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
|
||||
this.properties = prepareProperties(properties);
|
||||
this.codeMapping = codeMapping;
|
||||
public AbstractSmsClient(SmsChannelProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,74 +41,13 @@ public abstract class AbstractSmsClient implements SmsClient {
|
|||
return;
|
||||
}
|
||||
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
|
||||
this.properties = prepareProperties(properties);
|
||||
// 初始化
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在赋值给{@link this#properties}前,子类可根据需要预处理短信渠道配置
|
||||
*
|
||||
* @param properties 数据库中存储的短信渠道配置
|
||||
* @return 满足子类实现的短信渠道配置
|
||||
*/
|
||||
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return properties.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
|
||||
// 执行短信发送
|
||||
SmsCommonResult<SmsSendRespDTO> result;
|
||||
try {
|
||||
result = doSendSms(logId, mobile, apiTemplateId, templateParams);
|
||||
} catch (Throwable ex) {
|
||||
// 打印异常日志
|
||||
log.error("[sendSms][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
|
||||
logId, mobile, apiTemplateId, templateParams, ex);
|
||||
// 封装返回
|
||||
return SmsCommonResult.error(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams)
|
||||
throws Throwable;
|
||||
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
|
||||
try {
|
||||
return doParseSmsReceiveStatus(text);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[parseSmsReceiveStatus][text({}) 解析发生异常]", text, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable;
|
||||
|
||||
@Override
|
||||
public SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId) {
|
||||
// 执行短信发送
|
||||
SmsCommonResult<SmsTemplateRespDTO> result;
|
||||
try {
|
||||
result = doGetSmsTemplate(apiTemplateId);
|
||||
} catch (Throwable ex) {
|
||||
// 打印异常日志
|
||||
log.error("[getSmsTemplate][获得短信模板({}) 发生异常]", apiTemplateId, ex);
|
||||
// 封装返回
|
||||
return SmsCommonResult.error(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.aliyuncs.AcsRequest;
|
||||
import com.aliyuncs.AcsResponse;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
@ -31,9 +27,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
|
@ -46,6 +41,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
|
|||
@Slf4j
|
||||
public class AliyunSmsClient extends AbstractSmsClient {
|
||||
|
||||
/**
|
||||
* 调用成功 code
|
||||
*/
|
||||
public static final String API_CODE_SUCCESS = "OK";
|
||||
|
||||
/**
|
||||
* REGION, 使用杭州
|
||||
*/
|
||||
|
@ -57,7 +57,7 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||
private volatile IAcsClient client;
|
||||
|
||||
public AliyunSmsClient(SmsChannelProperties properties) {
|
||||
super(properties, new AliyunSmsCodeMapping());
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
@ -69,9 +69,9 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
|
||||
// 构建参数
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
SendSmsRequest request = new SendSmsRequest();
|
||||
request.setPhoneNumbers(mobile);
|
||||
request.setSignName(properties.getSignature());
|
||||
|
@ -79,34 +79,32 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
||||
request.setOutId(String.valueOf(sendLogId));
|
||||
// 执行请求
|
||||
return invoke(request, response -> new SmsSendRespDTO().setSerialNo(response.getBizId()));
|
||||
SendSmsResponse response = client.getAcsResponse(request);
|
||||
return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())
|
||||
.setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return statuses.stream().map(status -> {
|
||||
SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
|
||||
resp.setSuccess(status.getSuccess());
|
||||
resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg());
|
||||
resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
|
||||
resp.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()));
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
|
||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
|
||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
|
||||
.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
|
||||
// 构建参数
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 构建请求
|
||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
||||
request.setTemplateCode(apiTemplateId);
|
||||
// 执行请求
|
||||
return invoke(request, response -> {
|
||||
SmsTemplateRespDTO data = new SmsTemplateRespDTO();
|
||||
data.setId(response.getTemplateCode()).setContent(response.getTemplateContent());
|
||||
data.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
|
||||
return data;
|
||||
});
|
||||
QuerySmsTemplateResponse response = client.getAcsResponse(request);
|
||||
if (response.getTemplateStatus() == null) {
|
||||
return null;
|
||||
}
|
||||
return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
|
||||
.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -119,37 +117,10 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
<T extends AcsResponse, R> SmsCommonResult<R> invoke(AcsRequest<T> request, Function<T, R> responseConsumer) {
|
||||
try {
|
||||
// 执行发送. 由于阿里云 sms 短信没有统一的 Response,但是有统一的 code、message、requestId 属性,所以只好反射
|
||||
T sendResult = client.getAcsResponse(request);
|
||||
String code = (String) ReflectUtil.getFieldValue(sendResult, "code");
|
||||
String message = (String) ReflectUtil.getFieldValue(sendResult, "message");
|
||||
String requestId = (String) ReflectUtil.getFieldValue(sendResult, "requestId");
|
||||
// 解析结果
|
||||
R data = null;
|
||||
if (Objects.equals(code, "OK")) { // 请求成功的情况下
|
||||
data = responseConsumer.apply(sendResult);
|
||||
}
|
||||
// 拼接结果
|
||||
return SmsCommonResult.build(code, message, requestId, data, codeMapping);
|
||||
} catch (ClientException ex) {
|
||||
return SmsCommonResult.build(ex.getErrCode(), formatResultMsg(ex), ex.getRequestId(), null, codeMapping);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatResultMsg(ClientException ex) {
|
||||
if (StrUtil.isEmpty(ex.getErrorDescription())) {
|
||||
return ex.getErrMsg();
|
||||
}
|
||||
return ex.getErrMsg() + " => " + ex.getErrorDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信接收状态
|
||||
*
|
||||
* 参见 https://help.aliyun.com/document_detail/101867.html 文档
|
||||
* 参见 <a href="https://help.aliyun.com/document_detail/101867.html">文档</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
|
||||
/**
|
||||
* 阿里云的 SmsCodeMapping 实现类
|
||||
*
|
||||
* 参见 https://help.aliyun.com/document_detail/101346.htm 文档
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AliyunSmsCodeMapping implements SmsCodeMapping {
|
||||
|
||||
@Override
|
||||
public ErrorCode apply(String apiCode) {
|
||||
switch (apiCode) {
|
||||
case "OK": return GlobalErrorCodeConstants.SUCCESS;
|
||||
case "isv.ACCOUNT_NOT_EXISTS":
|
||||
case "isv.ACCOUNT_ABNORMAL":
|
||||
case "MissingAccessKeyId": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID;
|
||||
case "isp.RAM_PERMISSION_DENY": return SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY;
|
||||
case "isv.INVALID_JSON_PARAM":
|
||||
case "isv.INVALID_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR;
|
||||
case "isv.BUSINESS_LIMIT_CONTROL": return SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL;
|
||||
case "isv.DAY_LIMIT_CONTROL": return SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL;
|
||||
case "isv.SMS_CONTENT_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID;
|
||||
case "isv.SMS_TEMPLATE_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID;
|
||||
case "isv.SMS_SIGNATURE_ILLEGAL":
|
||||
case "isv.SIGN_NAME_ILLEGAL":
|
||||
case "isv.SMS_SIGN_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID;
|
||||
case "isv.AMOUNT_NOT_ENOUGH":
|
||||
case "isv.OUT_OF_SERVICE": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
|
||||
case "isv.MOBILE_NUMBER_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID;
|
||||
case "isv.TEMPLATE_MISSING_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
|
||||
default: return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.debug;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 钉钉的 SmsCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DebugDingTalkCodeMapping implements SmsCodeMapping {
|
||||
|
||||
@Override
|
||||
public ErrorCode apply(String apiCode) {
|
||||
return Objects.equals(apiCode, "0") ? GlobalErrorCodeConstants.SUCCESS : SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
|
@ -8,19 +8,19 @@ import cn.hutool.crypto.digest.DigestUtil;
|
|||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于钉钉 WebHook 实现的调试的短信客户端实现类
|
||||
|
@ -32,7 +32,7 @@ import java.util.Map;
|
|||
public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
||||
|
||||
public DebugDingTalkSmsClient(SmsChannelProperties properties) {
|
||||
super(properties, new DebugDingTalkCodeMapping());
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
String url = buildUrl("robot/send");
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
@ -55,14 +55,15 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||
String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));
|
||||
// 解析结果
|
||||
Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
|
||||
return SmsCommonResult.build(MapUtil.getStr(responseObj, "errcode"), MapUtil.getStr(responseObj, "errorMsg"),
|
||||
null, new SmsSendRespDTO().setSerialNo(StrUtil.uuid()), codeMapping);
|
||||
String errorCode = MapUtil.getStr(responseObj, "errcode");
|
||||
return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, "0")).setSerialNo(StrUtil.uuid())
|
||||
.setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, "errorMsg"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求地址
|
||||
*
|
||||
* 参见 https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71 文档
|
||||
* 参见 <a href="https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71">文档</a>
|
||||
*
|
||||
* @param path 请求路径
|
||||
* @return 请求地址
|
||||
|
@ -82,15 +83,14 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
|
||||
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) {
|
||||
return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
|
||||
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
|
||||
return SmsCommonResult.build("0", "success", null, data, codeMapping);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 腾讯云短信配置实现类
|
||||
* 腾讯云发送短信时,需要额外的参数 sdkAppId,
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
@Data
|
||||
public class TencentSmsChannelProperties extends SmsChannelProperties {
|
||||
|
||||
/**
|
||||
* 应用 id
|
||||
*/
|
||||
private String sdkAppId;
|
||||
|
||||
/**
|
||||
* 考虑到不破坏原有的 apiKey + apiSecret 的结构,
|
||||
* 所以腾讯云短信存储时,将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
* 因此在使用时,需要将 secretId 和 sdkAppId 解析出来,分别存储到对应字段中。
|
||||
*/
|
||||
public static TencentSmsChannelProperties build(SmsChannelProperties properties) {
|
||||
if (properties instanceof TencentSmsChannelProperties) {
|
||||
return (TencentSmsChannelProperties) properties;
|
||||
}
|
||||
TencentSmsChannelProperties result = BeanUtil.toBean(properties, TencentSmsChannelProperties.class);
|
||||
String combineKey = properties.getApiKey();
|
||||
Assert.notEmpty(combineKey, "apiKey 不能为空");
|
||||
String[] keys = combineKey.trim().split(" ");
|
||||
Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
|
||||
Assert.notBlank(keys[0], "腾讯云短信 secretId 不能为空");
|
||||
Assert.notBlank(keys[1], "腾讯云短信 sdkAppId 不能为空");
|
||||
result.setSdkAppId(keys[1]).setApiKey(keys[0]);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,7 @@ import cn.hutool.core.lang.Assert;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
|
@ -17,23 +15,22 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 腾讯云短信功能实现
|
||||
* <p>
|
||||
* 参见 https://cloud.tencent.com/document/product/382/52077
|
||||
*
|
||||
* 参见 <a href="https://cloud.tencent.com/document/product/382/52077">文档</a>
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
|
@ -42,7 +39,7 @@ public class TencentSmsClient extends AbstractSmsClient {
|
|||
/**
|
||||
* 调用成功 code
|
||||
*/
|
||||
public static final String API_SUCCESS_CODE = "Ok";
|
||||
public static final String API_CODE_SUCCESS = "Ok";
|
||||
|
||||
/**
|
||||
* REGION,使用南京
|
||||
|
@ -51,180 +48,103 @@ public class TencentSmsClient extends AbstractSmsClient {
|
|||
|
||||
/**
|
||||
* 是否国际/港澳台短信:
|
||||
*
|
||||
* 0:表示国内短信。
|
||||
* 1:表示国际/港澳台短信。
|
||||
*/
|
||||
private static final long INTERNATIONAL = 0L;
|
||||
private static final long INTERNATIONAL_CHINA = 0L;
|
||||
|
||||
private SmsClient client;
|
||||
|
||||
public TencentSmsClient(SmsChannelProperties properties) {
|
||||
super(properties, new TencentSmsCodeMapping());
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
validateSdkAppId(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
|
||||
Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
|
||||
Credential credential = new Credential(getApiKey(), properties.getApiSecret());
|
||||
client = new SmsClient(credential, ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验腾讯云的 SDK AppId
|
||||
*
|
||||
* 原因是:腾讯云发放短信的时候,需要额外的参数 sdkAppId
|
||||
*
|
||||
* 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
*
|
||||
* @param properties 配置
|
||||
*/
|
||||
private static void validateSdkAppId(SmsChannelProperties properties) {
|
||||
String combineKey = properties.getApiKey();
|
||||
Assert.notEmpty(combineKey, "apiKey 不能为空");
|
||||
String[] keys = combineKey.trim().split(" ");
|
||||
Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
|
||||
}
|
||||
|
||||
private String getSdkAppId() {
|
||||
return StrUtil.subAfter(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
private String getApiKey() {
|
||||
return StrUtil.subBefore(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId,
|
||||
String mobile,
|
||||
String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
return invoke(() -> buildSendSmsRequest(sendLogId, mobile, apiTemplateId, templateParams),
|
||||
this::doSendSms0,
|
||||
response -> {
|
||||
SendStatus sendStatus = response.getSendStatusSet()[0];
|
||||
return SmsCommonResult.build(sendStatus.getCode(), sendStatus.getMessage(), response.getRequestId(),
|
||||
new SmsSendRespDTO().setSerialNo(sendStatus.getSerialNo()), codeMapping);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 腾讯云发放短信的时候,需要额外的参数 sdkAppId。
|
||||
* 考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
* 因此,这边需要使用 TencentSmsChannelProperties 做拆分,重新封装到 properties 内。
|
||||
*
|
||||
* @param properties 数据库中存储的短信渠道配置
|
||||
* @return TencentSmsChannelProperties
|
||||
*/
|
||||
@Override
|
||||
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
|
||||
return TencentSmsChannelProperties.build(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用腾讯云 SDK 发送短信
|
||||
*
|
||||
* @param request 发送短信请求
|
||||
* @return 发送短信响应
|
||||
* @throws TencentCloudSDKException SDK 用来封装发送短信失败
|
||||
*/
|
||||
private SendSmsResponse doSendSms0(SendSmsRequest request) throws TencentCloudSDKException {
|
||||
return client.SendSms(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装腾讯云发送短信请求
|
||||
*
|
||||
* @param sendLogId 日志编号
|
||||
* @param mobile 手机号
|
||||
* @param apiTemplateId 短信 API 的模板编号
|
||||
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
|
||||
* @return 腾讯云发送短信请求
|
||||
*/
|
||||
private SendSmsRequest buildSendSmsRequest(Long sendLogId,
|
||||
String mobile,
|
||||
String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) {
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
SendSmsRequest request = new SendSmsRequest();
|
||||
request.setSmsSdkAppId(((TencentSmsChannelProperties) properties).getSdkAppId());
|
||||
request.setSmsSdkAppId(getSdkAppId());
|
||||
request.setPhoneNumberSet(new String[]{mobile});
|
||||
request.setSignName(properties.getSignature());
|
||||
request.setTemplateId(apiTemplateId);
|
||||
request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
|
||||
request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));
|
||||
return request;
|
||||
// 执行请求
|
||||
SendSmsResponse response = client.SendSms(request);
|
||||
SendStatus status = response.getSendStatusSet()[0];
|
||||
return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo())
|
||||
.setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return CollectionUtils.convertList(callback, status -> {
|
||||
SmsReceiveRespDTO data = new SmsReceiveRespDTO();
|
||||
data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription());
|
||||
data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()));
|
||||
data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
|
||||
SessionContext context;
|
||||
Long logId;
|
||||
Assert.notNull(context = status.getSessionContext(), "回执信息中未解析出 context,请联系腾讯云小助手");
|
||||
Assert.notNull(logId = context.getLogId(), "回执信息中未解析出 logId,请联系腾讯云小助手");
|
||||
data.setLogId(logId);
|
||||
return data;
|
||||
});
|
||||
return convertList(callback, status -> new SmsReceiveRespDTO()
|
||||
.setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
|
||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())
|
||||
.setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())
|
||||
.setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
return invoke(() -> this.buildSmsTemplateStatusRequest(apiTemplateId),
|
||||
this::doGetSmsTemplate0,
|
||||
response -> {
|
||||
SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]);
|
||||
return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping);
|
||||
});
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 构建请求
|
||||
DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
|
||||
request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
|
||||
request.setInternational(INTERNATIONAL_CHINA);
|
||||
// 执行请求
|
||||
DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request);
|
||||
DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0];
|
||||
if (status == null || status.getStatusCode() == null) {
|
||||
return null;
|
||||
}
|
||||
return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent())
|
||||
.setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SmsTemplateRespDTO convertTemplateStatusDTO(DescribeTemplateListStatus templateStatus) {
|
||||
if (templateStatus == null) {
|
||||
return null;
|
||||
Integer convertSmsTemplateAuditStatus(int templateStatus) {
|
||||
switch (templateStatus) {
|
||||
case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
|
||||
case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
|
||||
case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
|
||||
default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
|
||||
}
|
||||
SmsTemplateAuditStatusEnum auditStatus;
|
||||
Assert.notNull(templateStatus.getStatusCode(),
|
||||
StrUtil.format("短信模版审核状态为 null,模版 id{}", templateStatus.getTemplateId()));
|
||||
switch (templateStatus.getStatusCode().intValue()) {
|
||||
case -1:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.FAIL;
|
||||
break;
|
||||
case 0:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.SUCCESS;
|
||||
break;
|
||||
case 1:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.CHECKING;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(StrUtil.format("不能解析短信模版审核状态{},模版 id{}",
|
||||
templateStatus.getStatusCode(), templateStatus.getTemplateId()));
|
||||
}
|
||||
SmsTemplateRespDTO data = new SmsTemplateRespDTO();
|
||||
data.setId(String.valueOf(templateStatus.getTemplateId())).setContent(templateStatus.getTemplateContent());
|
||||
data.setAuditStatus(auditStatus.getStatus()).setAuditReason(templateStatus.getReviewReply());
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装查询模版审核状态请求
|
||||
* @param apiTemplateId api 的模版 id
|
||||
* @return 查询模版审核状态请求
|
||||
*/
|
||||
private DescribeSmsTemplateListRequest buildSmsTemplateStatusRequest(String apiTemplateId) {
|
||||
DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
|
||||
request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
|
||||
// 地区 0:表示国内短信。1:表示国际/港澳台短信。
|
||||
request.setInternational(INTERNATIONAL);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用腾讯云 SDK 查询短信模版状态
|
||||
*
|
||||
* @param request 查询短信模版状态请求
|
||||
* @return 查询短信模版状态响应
|
||||
* @throws TencentCloudSDKException SDK 用来封装查询短信模版状态失败
|
||||
*/
|
||||
private DescribeSmsTemplateListResponse doGetSmsTemplate0(DescribeSmsTemplateListRequest request) throws TencentCloudSDKException {
|
||||
return client.DescribeSmsTemplateList(request);
|
||||
}
|
||||
|
||||
<Q, P, R> SmsCommonResult<R> invoke(Supplier<Q> requestSupplier,
|
||||
SdkFunction<Q, P> responseSupplier,
|
||||
Function<P, SmsCommonResult<R>> resultGen) {
|
||||
// 构建请求body
|
||||
Q request = requestSupplier.get();
|
||||
P response;
|
||||
// 调用腾讯云发送短信
|
||||
try {
|
||||
response = responseSupplier.apply(request);
|
||||
} catch (TencentCloudSDKException e) {
|
||||
// 调用异常,封装结果
|
||||
return SmsCommonResult.build(e.getErrorCode(), e.getMessage(), e.getRequestId(), null, codeMapping);
|
||||
}
|
||||
return resultGen.apply(response);
|
||||
}
|
||||
|
||||
@Data
|
||||
|
@ -278,7 +198,7 @@ public class TencentSmsClient extends AbstractSmsClient {
|
|||
private String serialNo;
|
||||
|
||||
/**
|
||||
* 用户的 session 内容(与发送接口的请求参数SessionContext一致)
|
||||
* 用户的 session 内容(与发送接口的请求参数 SessionContext 一致)
|
||||
*/
|
||||
@JsonProperty("ext")
|
||||
private SessionContext sessionContext;
|
||||
|
@ -293,10 +213,7 @@ public class TencentSmsClient extends AbstractSmsClient {
|
|||
* 发送短信记录id
|
||||
*/
|
||||
private Long logId;
|
||||
}
|
||||
|
||||
private interface SdkFunction<T, R> {
|
||||
R apply(T t) throws TencentCloudSDKException;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
|
||||
import static cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 腾讯云的 SmsCodeMapping 实现类
|
||||
*
|
||||
* 参见 https://cloud.tencent.com/document/api/382/52075#.E5.85.AC.E5.85.B1.E9.94.99.E8.AF.AF.E7.A0.81
|
||||
*
|
||||
* @author : shiwp
|
||||
*/
|
||||
public class TencentSmsCodeMapping implements SmsCodeMapping {
|
||||
|
||||
@Override
|
||||
public ErrorCode apply(String apiCode) {
|
||||
switch (apiCode) {
|
||||
case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
|
||||
case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
|
||||
case "FailedOperation.JsonParseFail":
|
||||
case "MissingParameter.EmptyPhoneNumberSet":
|
||||
case "LimitExceeded.PhoneNumberCountLimit":
|
||||
case "FailedOperation.FailResolvePacket": return GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
case "FailedOperation.InsufficientBalanceInSmsPackage": return SMS_ACCOUNT_MONEY_NOT_ENOUGH;
|
||||
case "FailedOperation.MarketingSendTimeConstraint": return SMS_SEND_MARKET_LIMIT_CONTROL;
|
||||
case "FailedOperation.PhoneNumberInBlacklist": return SMS_MOBILE_BLACK;
|
||||
case "FailedOperation.SignatureIncorrectOrUnapproved": return SMS_SIGN_INVALID;
|
||||
case "FailedOperation.MissingTemplateToModify":
|
||||
case "FailedOperation.TemplateIncorrectOrUnapproved": return SMS_TEMPLATE_INVALID;
|
||||
case "InvalidParameterValue.IncorrectPhoneNumber": return SMS_MOBILE_INVALID;
|
||||
case "InvalidParameterValue.SdkAppIdNotExist": return SMS_APP_ID_INVALID;
|
||||
case "InvalidParameterValue.TemplateParameterLengthLimit":
|
||||
case "InvalidParameterValue.TemplateParameterFormatError": return SMS_TEMPLATE_PARAM_ERROR;
|
||||
case "LimitExceeded.PhoneNumberDailyLimit": return SMS_SEND_DAY_LIMIT_CONTROL;
|
||||
case "LimitExceeded.PhoneNumberThirtySecondLimit":
|
||||
case "LimitExceeded.PhoneNumberOneHourLimit": return SMS_SEND_BUSINESS_LIMIT_CONTROL;
|
||||
case "UnauthorizedOperation.RequestPermissionDeny":
|
||||
case "FailedOperation.ForbidAddMarketingTemplates":
|
||||
case "FailedOperation.NotEnterpriseCertification":
|
||||
case "UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny": return SMS_PERMISSION_DENY;
|
||||
case "UnauthorizedOperation.RequestIpNotInWhitelist": return SMS_IP_DENY;
|
||||
case "AuthFailure.SecretIdNotFound": return SMS_ACCOUNT_INVALID;
|
||||
}
|
||||
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
}
|
|
@ -1,26 +1,20 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import com.aliyuncs.AcsRequest;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.InjectMocks;
|
||||
|
@ -28,12 +22,10 @@ import org.mockito.Mock;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -67,8 +59,7 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testDoSendSms() throws ClientException {
|
||||
public void tesSendSms_success() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
|
@ -87,20 +78,47 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertTrue(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getBizId(), result.getData().getSerialNo());
|
||||
assertEquals(response.getBizId(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoTParseSmsReceiveStatus() throws Throwable {
|
||||
public void tesSendSms_fail() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR"));
|
||||
when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
|
||||
assertEquals(mobile, acsRequest.getPhoneNumbers());
|
||||
assertEquals(properties.getSignature(), acsRequest.getSignName());
|
||||
assertEquals(apiTemplateId, acsRequest.getTemplateCode());
|
||||
assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
|
||||
assertEquals(sendLogId.toString(), acsRequest.getOutId());
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertFalse(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getBizId(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSmsReceiveStatus() {
|
||||
// 准备参数
|
||||
String text = "[\n" +
|
||||
" {\n" +
|
||||
|
@ -118,20 +136,21 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
|||
// mock 方法
|
||||
|
||||
// 调用
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
|
||||
// 断言
|
||||
assertEquals(1, statuses.size());
|
||||
assertTrue(statuses.get(0).getSuccess());
|
||||
assertEquals("DELIVERED", statuses.get(0).getErrorCode());
|
||||
assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
|
||||
assertEquals("13900000001", statuses.get(0).getMobile());
|
||||
assertEquals(LocalDateTime.of(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
|
||||
assertEquals(LocalDateTime.of(2017, 2, 2, 22, 23, 24),
|
||||
statuses.get(0).getReceiveTime());
|
||||
assertEquals("12345", statuses.get(0).getSerialNo());
|
||||
assertEquals(67890L, statuses.get(0).getLogId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoGetSmsTemplate() throws ClientException {
|
||||
public void testGetSmsTemplate() throws Throwable {
|
||||
// 准备参数
|
||||
String apiTemplateId = randomString();
|
||||
// mock 方法
|
||||
|
@ -145,18 +164,12 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId);
|
||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
|
||||
// 断言
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getTemplateCode(), result.getData().getId());
|
||||
assertEquals(response.getTemplateContent(), result.getData().getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
|
||||
assertEquals(response.getReason(), result.getData().getAuditReason());
|
||||
assertEquals(response.getTemplateCode(), result.getId());
|
||||
assertEquals(response.getTemplateContent(), result.getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||
assertEquals(response.getReason(), result.getAuditReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -171,55 +184,4 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
|||
"未知审核状态(3)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testInvoke_throwable() throws ClientException {
|
||||
// 准备参数
|
||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
||||
// mock 方法
|
||||
ClientException ex = new ClientException("isv.INVALID_PARAMETERS", "参数不正确", randomString());
|
||||
when(client.getAcsResponse(any(AcsRequest.class))).thenThrow(ex);
|
||||
|
||||
// 调用,并断言异常
|
||||
SmsCommonResult<?> result = smsClient.invoke(request, null);
|
||||
// 断言
|
||||
assertEquals(ex.getErrCode(), result.getApiCode());
|
||||
assertEquals(ex.getErrMsg(), result.getApiMsg());
|
||||
Assertions.assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR.getCode(), result.getCode());
|
||||
Assertions.assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR.getMsg(), result.getMsg());
|
||||
assertEquals(ex.getRequestId(), result.getApiRequestId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvoke_success() throws ClientException {
|
||||
// 准备参数
|
||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
||||
Function<QuerySmsTemplateResponse, SmsTemplateRespDTO> responseConsumer = response -> {
|
||||
SmsTemplateRespDTO data = new SmsTemplateRespDTO();
|
||||
data.setId(response.getTemplateCode()).setContent(response.getTemplateContent());
|
||||
data.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(response.getReason());
|
||||
return data;
|
||||
};
|
||||
// mock 方法
|
||||
QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
|
||||
o.setCode("OK");
|
||||
o.setTemplateStatus(1); // 设置模板通过
|
||||
});
|
||||
when(client.getAcsResponse(any(AcsRequest.class))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.invoke(request, responseConsumer);
|
||||
// 断言
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getTemplateCode(), result.getData().getId());
|
||||
assertEquals(response.getTemplateContent(), result.getData().getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
|
||||
assertEquals(response.getReason(), result.getData().getAuditReason());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link AliyunSmsCodeMapping} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AliyunSmsCodeMappingTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private AliyunSmsCodeMapping codeMapping;
|
||||
|
||||
@Test
|
||||
public void testApply() {
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("OK"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("MissingAccessKeyId"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_NOT_EXISTS"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_ABNORMAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("isv.DAY_LIMIT_CONTROL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("isv.SMS_CONTENT_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGN_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SIGN_NAME_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("isp.RAM_PERMISSION_DENY"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.OUT_OF_SERVICE"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.AMOUNT_NOT_ENOUGH"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("isv.SMS_TEMPLATE_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGNATURE_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_PARAMETERS"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_JSON_PARAM"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("isv.MOBILE_NUMBER_ILLEGAL"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("isv.TEMPLATE_MISSING_PARAMETERS"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("isv.BUSINESS_LIMIT_CONTROL"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
|
@ -31,7 +28,6 @@ import java.util.List;
|
|||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -78,7 +74,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms() throws Throwable {
|
||||
public void testDoSendSms_success() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
|
@ -94,7 +90,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
o.setSendStatusSet(sendStatuses);
|
||||
SendStatus sendStatus = new SendStatus();
|
||||
sendStatuses[0] = sendStatus;
|
||||
sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
|
||||
sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
|
||||
sendStatus.setMessage("send success");
|
||||
sendStatus.setSerialNo(serialNo);
|
||||
});
|
||||
|
@ -109,20 +105,58 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertTrue(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getData().getSerialNo());
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoTParseSmsReceiveStatus() throws Throwable {
|
||||
public void testDoSendSms_fail() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
String requestId = randomString();
|
||||
String serialNo = randomString();
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||
o.setRequestId(requestId);
|
||||
SendStatus[] sendStatuses = new SendStatus[1];
|
||||
o.setSendStatusSet(sendStatuses);
|
||||
SendStatus sendStatus = new SendStatus();
|
||||
sendStatuses[0] = sendStatus;
|
||||
sendStatus.setCode("ERROR");
|
||||
sendStatus.setMessage("send success");
|
||||
sendStatus.setSerialNo(serialNo);
|
||||
});
|
||||
when(client.SendSms(argThat(request -> {
|
||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||
assertEquals(properties.getSignature(), request.getSignName());
|
||||
assertEquals(apiTemplateId, request.getTemplateId());
|
||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||
toJsonString(request.getTemplateParamSet()));
|
||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertFalse(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSmsReceiveStatus() {
|
||||
// 准备参数
|
||||
String text = "[\n" +
|
||||
" {\n" +
|
||||
|
@ -139,7 +173,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
// mock 方法
|
||||
|
||||
// 调用
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
|
||||
// 断言
|
||||
assertEquals(1, statuses.size());
|
||||
assertTrue(statuses.get(0).getSuccess());
|
||||
|
@ -152,7 +186,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDoGetSmsTemplate() throws Throwable {
|
||||
public void testGetSmsTemplate() throws Throwable {
|
||||
// 准备参数
|
||||
Long apiTemplateId = randomLongId();
|
||||
String requestId = randomString();
|
||||
|
@ -173,50 +207,24 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
|||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
|
||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
|
||||
// 断言
|
||||
assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
|
||||
assertNull(result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getData().getId());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getData().getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertSuccessTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertCheckingTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertFailTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertUnknownTemplateStatus() {
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setStatusCode(3L);
|
||||
Long templateId = randomLongId();
|
||||
// 调用,并断言结果
|
||||
assertThrows(IllegalStateException.class, () -> smsClient.convertTemplateStatusDTO(templateStatus),
|
||||
StrUtil.format("不能解析短信模版审核状态[3],模版id[{}]", templateId));
|
||||
}
|
||||
|
||||
private void testTemplateStatus(SmsTemplateAuditStatusEnum expected, Long value) {
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setStatusCode(value);
|
||||
SmsTemplateRespDTO result = smsClient.convertTemplateStatusDTO(templateStatus);
|
||||
assertEquals(expected.getStatus(), result.getAuditStatus());
|
||||
public void testConvertSmsTemplateAuditStatus() {
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(0));
|
||||
assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(1));
|
||||
assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),
|
||||
"未知审核状态(3)");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link TencentSmsCodeMapping} 的单元测试
|
||||
*
|
||||
* @author : shiwp
|
||||
*/
|
||||
public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private TencentSmsCodeMapping codeMapping;
|
||||
|
||||
@Test
|
||||
public void testApply() {
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("LimitExceeded.PhoneNumberCountLimit"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.FailResolvePacket"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("FailedOperation.InsufficientBalanceInSmsPackage"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_MARKET_LIMIT_CONTROL, codeMapping.apply("FailedOperation.MarketingSendTimeConstraint"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_BLACK, codeMapping.apply("FailedOperation.PhoneNumberInBlacklist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("FailedOperation.SignatureIncorrectOrUnapproved"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.MissingTemplateToModify"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.TemplateIncorrectOrUnapproved"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("InvalidParameterValue.IncorrectPhoneNumber"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_APP_ID_INVALID, codeMapping.apply("InvalidParameterValue.SdkAppIdNotExist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterLengthLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterFormatError"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberDailyLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberThirtySecondLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberOneHourLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.RequestPermissionDeny"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.ForbidAddMarketingTemplates"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.NotEnterpriseCertification"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_IP_DENY, codeMapping.apply("UnauthorizedOperation.RequestIpNotInWhitelist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("AuthFailure.SecretIdNotFound"));
|
||||
}
|
||||
|
||||
}
|
|
@ -82,6 +82,10 @@ public interface ErrorCodeConstants {
|
|||
// ========== 短信模板 1-002-012-000 ==========
|
||||
ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在");
|
||||
ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_012_001, "已经存在编码为【{}】的短信模板");
|
||||
ErrorCode SMS_TEMPLATE_API_ERROR = new ErrorCode(1_002_012_002, "短信 API 模板调用失败,原因是:{}");
|
||||
ErrorCode SMS_TEMPLATE_API_AUDIT_CHECKING = new ErrorCode(1_002_012_003, "短信 API 模版无法使用,原因:审批中");
|
||||
ErrorCode SMS_TEMPLATE_API_AUDIT_FAIL = new ErrorCode(1_002_012_004, "短信 API 模版无法使用,原因:审批不通过,{}");
|
||||
ErrorCode SMS_TEMPLATE_API_NOT_FOUND = new ErrorCode(1_002_012_005, "短信 API 模版无法使用,原因:模版不存在");
|
||||
|
||||
// ========== 短信发送 1-002-013-000 ==========
|
||||
ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1_002_013_000, "手机号不存在");
|
||||
|
|
|
@ -63,12 +63,6 @@ public class SmsLogExcelVO {
|
|||
@ExcelProperty("发送时间")
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
@ExcelProperty("发送结果的编码")
|
||||
private Integer sendCode;
|
||||
|
||||
@ExcelProperty("发送结果的提示")
|
||||
private String sendMsg;
|
||||
|
||||
@ExcelProperty("短信 API 发送结果的编码")
|
||||
private String apiSendCode;
|
||||
|
||||
|
|
|
@ -35,4 +35,4 @@ public class SmsLogExportReqVO {
|
|||
@Schema(description = "开始接收时间")
|
||||
private LocalDateTime[] receiveTime;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,12 +52,6 @@ public class SmsLogRespVO {
|
|||
@Schema(description = "发送时间")
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
@Schema(description = "发送结果的编码", example = "0")
|
||||
private Integer sendCode;
|
||||
|
||||
@Schema(description = "发送结果的提示", example = "成功")
|
||||
private String sendMsg;
|
||||
|
||||
@Schema(description = "短信 API 发送结果的编码", example = "SUCCESS")
|
||||
private String apiSendCode;
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package cn.iocoder.yudao.module.system.dal.dataobject.sms;
|
||||
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsReceiveStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsReceiveStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -115,19 +114,6 @@ public class SmsLogDO extends BaseDO {
|
|||
* 发送时间
|
||||
*/
|
||||
private LocalDateTime sendTime;
|
||||
/**
|
||||
* 发送结果的编码
|
||||
*
|
||||
* 枚举 {@link SmsFrameworkErrorCodeConstants}
|
||||
*/
|
||||
private Integer sendCode;
|
||||
/**
|
||||
* 发送结果的提示
|
||||
*
|
||||
* 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
|
||||
* 异常情况下,通过格式化 Exception 的提示存储
|
||||
*/
|
||||
private String sendMsg;
|
||||
/**
|
||||
* 短信 API 发送结果的编码
|
||||
*
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
@ -37,15 +37,15 @@ public interface SmsLogService {
|
|||
* 更新日志的发送结果
|
||||
*
|
||||
* @param id 日志编号
|
||||
* @param sendCode 发送结果的编码
|
||||
* @param sendMsg 发送结果的提示
|
||||
* @param success 发送是否成功
|
||||
* @param apiSendCode 短信 API 发送结果的编码
|
||||
* @param apiSendMsg 短信 API 发送失败的提示
|
||||
* @param apiRequestId 短信 API 发送返回的唯一请求 ID
|
||||
* @param apiSerialNo 短信 API 发送返回的序号
|
||||
*/
|
||||
void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
|
||||
String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo);
|
||||
void updateSmsSendResult(Long id, Boolean success,
|
||||
String apiSendCode, String apiSendMsg,
|
||||
String apiRequestId, String apiSerialNo);
|
||||
|
||||
/**
|
||||
* 更新日志的接收结果
|
||||
|
@ -56,7 +56,8 @@ public interface SmsLogService {
|
|||
* @param apiReceiveCode API 接收结果的编码
|
||||
* @param apiReceiveMsg API 接收结果的说明
|
||||
*/
|
||||
void updateSmsReceiveResult(Long id, Boolean success, LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg);
|
||||
void updateSmsReceiveResult(Long id, Boolean success,
|
||||
LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg);
|
||||
|
||||
/**
|
||||
* 获得短信日志分页
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsLogMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsReceiveStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -55,13 +54,12 @@ public class SmsLogServiceImpl implements SmsLogService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
|
||||
public void updateSmsSendResult(Long id, Boolean success,
|
||||
String apiSendCode, String apiSendMsg,
|
||||
String apiRequestId, String apiSerialNo) {
|
||||
SmsSendStatusEnum sendStatus = CommonResult.isSuccess(sendCode) ?
|
||||
SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
|
||||
smsLogMapper.updateById(SmsLogDO.builder().id(id).sendStatus(sendStatus.getStatus())
|
||||
.sendTime(LocalDateTime.now()).sendCode(sendCode).sendMsg(sendMsg)
|
||||
SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
|
||||
smsLogMapper.updateById(SmsLogDO.builder().id(id)
|
||||
.sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now())
|
||||
.apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)
|
||||
.apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
|
@ -8,7 +9,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
|||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
|
@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
|
|||
import cn.iocoder.yudao.module.system.service.member.MemberService;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -35,6 +36,7 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SmsSendServiceImpl implements SmsSendService {
|
||||
|
||||
@Resource
|
||||
|
@ -158,11 +160,17 @@ public class SmsSendServiceImpl implements SmsSendService {
|
|||
SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());
|
||||
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
|
||||
// 发送短信
|
||||
SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
|
||||
message.getApiTemplateId(), message.getTemplateParams());
|
||||
smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getMsg(),
|
||||
sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(),
|
||||
sendResult.getData() != null ? sendResult.getData().getSerialNo() : null);
|
||||
try {
|
||||
SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),
|
||||
message.getApiTemplateId(), message.getTemplateParams());
|
||||
smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
|
||||
sendResponse.getApiCode(), sendResponse.getApiMsg(),
|
||||
sendResponse.getApiRequestId(), sendResponse.getSerialNo());
|
||||
} catch (Throwable ex) {
|
||||
log.error("[doSendSms][发送短信异常,日志编号({})]", message.getLogId(), ex);
|
||||
smsLogService.updateSmsSendResult(message.getLogId(), false,
|
||||
"EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
|
||||
|
@ -21,11 +23,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
@ -171,9 +173,24 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
// 获得短信模板
|
||||
SmsClient smsClient = smsChannelService.getSmsClient(channelId);
|
||||
Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
|
||||
SmsCommonResult<SmsTemplateRespDTO> templateResult = smsClient.getSmsTemplate(apiTemplateId);
|
||||
// 校验短信模板是否正确
|
||||
templateResult.checkError();
|
||||
SmsTemplateRespDTO template;
|
||||
try {
|
||||
template = smsClient.getSmsTemplate(apiTemplateId);
|
||||
} catch (Throwable ex) {
|
||||
throw exception(SMS_TEMPLATE_API_ERROR, ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
// 校验短信模版
|
||||
if (template == null) {
|
||||
throw exception(SMS_TEMPLATE_API_NOT_FOUND);
|
||||
}
|
||||
if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.CHECKING.getStatus())) {
|
||||
throw exception(SMS_TEMPLATE_API_AUDIT_CHECKING);
|
||||
}
|
||||
if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.FAIL.getStatus())) {
|
||||
throw exception(SMS_TEMPLATE_API_AUDIT_FAIL, template.getAuditReason());
|
||||
}
|
||||
Assert.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
|
||||
String.format("短信模板(%s) 审核状态(%d) 不正确", apiTemplateId, template.getAuditStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -164,31 +164,31 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetSmsChannelPage() {
|
||||
// mock 数据
|
||||
SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class, o -> { // 等会查询到
|
||||
o.setSignature("芋道源码");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2020, 12, 12));
|
||||
});
|
||||
smsChannelMapper.insert(dbSmsChannel);
|
||||
// 测试 signature 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setSignature("源码")));
|
||||
// 测试 status 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11))));
|
||||
// 准备参数
|
||||
SmsChannelPageReqVO reqVO = new SmsChannelPageReqVO();
|
||||
reqVO.setSignature("芋道");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));
|
||||
// mock 数据
|
||||
SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class, o -> { // 等会查询到
|
||||
o.setSignature("芋道源码");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2020, 12, 12));
|
||||
});
|
||||
smsChannelMapper.insert(dbSmsChannel);
|
||||
// 测试 signature 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setSignature("源码")));
|
||||
// 测试 status 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11))));
|
||||
// 准备参数
|
||||
SmsChannelPageReqVO reqVO = new SmsChannelPageReqVO();
|
||||
reqVO.setSignature("芋道");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));
|
||||
|
||||
// 调用
|
||||
PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbSmsChannel, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbSmsChannel, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.service.sms;
|
|||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
@ -172,22 +171,20 @@ public class SmsLogServiceImplTest extends BaseDbUnitTest {
|
|||
smsLogMapper.insert(dbSmsLog);
|
||||
// 准备参数
|
||||
Long id = dbSmsLog.getId();
|
||||
Integer sendCode = randomInteger();
|
||||
String sendMsg = randomString();
|
||||
Boolean success = randomBoolean();
|
||||
String apiSendCode = randomString();
|
||||
String apiSendMsg = randomString();
|
||||
String apiRequestId = randomString();
|
||||
String apiSerialNo = randomString();
|
||||
|
||||
// 调用
|
||||
smsLogService.updateSmsSendResult(id, sendCode, sendMsg,
|
||||
smsLogService.updateSmsSendResult(id, success,
|
||||
apiSendCode, apiSendMsg, apiRequestId, apiSerialNo);
|
||||
// 断言
|
||||
dbSmsLog = smsLogMapper.selectById(id);
|
||||
assertEquals(CommonResult.isSuccess(sendCode) ? SmsSendStatusEnum.SUCCESS.getStatus()
|
||||
: SmsSendStatusEnum.FAILURE.getStatus(), dbSmsLog.getSendStatus());
|
||||
assertEquals(success ? SmsSendStatusEnum.SUCCESS.getStatus() : SmsSendStatusEnum.FAILURE.getStatus(),
|
||||
dbSmsLog.getSendStatus());
|
||||
assertNotNull(dbSmsLog.getSendTime());
|
||||
assertEquals(sendMsg, dbSmsLog.getSendMsg());
|
||||
assertEquals(apiSendCode, dbSmsLog.getApiSendCode());
|
||||
assertEquals(apiSendMsg, dbSmsLog.getApiSendMsg());
|
||||
assertEquals(apiRequestId, dbSmsLog.getApiRequestId());
|
||||
|
|
|
@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
|
|||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
|
@ -244,15 +243,14 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testDoSendSms() {
|
||||
public void testDoSendSms() throws Throwable {
|
||||
// 准备参数
|
||||
SmsSendMessage message = randomPojo(SmsSendMessage.class);
|
||||
// mock SmsClientFactory 的方法
|
||||
SmsClient smsClient = spy(SmsClient.class);
|
||||
when(smsChannelService.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient);
|
||||
// mock SmsClient 的方法
|
||||
SmsCommonResult<SmsSendRespDTO> sendResult = randomPojo(SmsCommonResult.class, SmsSendRespDTO.class);
|
||||
sendResult.setData(randomPojo(SmsSendRespDTO.class));
|
||||
SmsSendRespDTO sendResult = randomPojo(SmsSendRespDTO.class);
|
||||
when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()),
|
||||
eq(message.getTemplateParams()))).thenReturn(sendResult);
|
||||
|
||||
|
@ -260,8 +258,8 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
|||
smsService.doSendSms(message);
|
||||
// 断言
|
||||
verify(smsLogService).updateSmsSendResult(eq(message.getLogId()),
|
||||
eq(sendResult.getCode()), eq(sendResult.getMsg()), eq(sendResult.getApiCode()),
|
||||
eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getData().getSerialNo()));
|
||||
eq(sendResult.getSuccess()), eq(sendResult.getApiCode()),
|
||||
eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
|
||||
|
@ -65,7 +64,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testCreateSmsTemplate_success() {
|
||||
public void testCreateSmsTemplate_success() throws Throwable {
|
||||
// 准备参数
|
||||
SmsTemplateCreateReqVO reqVO = randomPojo(SmsTemplateCreateReqVO.class, o -> {
|
||||
o.setContent("正在进行登录操作{operation},您的验证码是{code}");
|
||||
|
@ -80,8 +79,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
|
||||
// mock 获得 API 短信模板成功
|
||||
when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
|
||||
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class,
|
||||
o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode())));
|
||||
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(
|
||||
randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));
|
||||
|
||||
// 调用
|
||||
Long smsTemplateId = smsTemplateService.createSmsTemplate(reqVO);
|
||||
|
@ -96,7 +95,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testUpdateSmsTemplate_success() {
|
||||
public void testUpdateSmsTemplate_success() throws Throwable {
|
||||
// mock 数据
|
||||
SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
|
||||
smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
|
||||
|
@ -115,8 +114,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
|
||||
// mock 获得 API 短信模板成功
|
||||
when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
|
||||
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class,
|
||||
o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode())));
|
||||
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(
|
||||
randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));
|
||||
|
||||
// 调用
|
||||
smsTemplateService.updateSmsTemplate(reqVO);
|
||||
|
|
Loading…
Reference in New Issue