sms:移除 SmsCodeMapping + SmsCommonResult,简化短信的封装

pull/67/head
YunaiV 2023-11-21 23:32:26 +08:00
parent 4118f25d75
commit 562f82580e
30 changed files with 346 additions and 874 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
*/

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretIdsecretKey
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, "手机号不存在");

View File

@ -63,12 +63,6 @@ public class SmsLogExcelVO {
@ExcelProperty("发送时间")
private LocalDateTime sendTime;
@ExcelProperty("发送结果的编码")
private Integer sendCode;
@ExcelProperty("发送结果的提示")
private String sendMsg;
@ExcelProperty("短信 API 发送结果的编码")
private String apiSendCode;

View File

@ -35,4 +35,4 @@ public class SmsLogExportReqVO {
@Schema(description = "开始接收时间")
private LocalDateTime[] receiveTime;
}
}

View File

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

View File

@ -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
*

View File

@ -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);
/**
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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