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

This commit is contained in:
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 数组保证参数的顺序 * @param templateParams 短信模板参数通过 List 数组保证参数的顺序
* @return 短信发送结果 * @return 短信发送结果
*/ */
SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId, SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
List<KeyValue<String, Object>> templateParams); List<KeyValue<String, Object>> templateParams) throws Throwable;
/** /**
* 解析接收短信的接收结果 * 解析接收短信的接收结果
@ -49,6 +49,6 @@ public interface SmsClient {
* @param apiTemplateId 短信 API 的模板编号 * @param apiTemplateId 短信 API 的模板编号
* @return 短信模板 * @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 @Data
public class SmsSendRespDTO { public class SmsSendRespDTO {
/**
* 是否成功
*/
private Boolean success;
/**
* API 请求编号
*/
private String apiRequestId;
// ==================== 成功时字段 ====================
/** /**
* 短信 API 发送返回的序号 * 短信 API 发送返回的序号
*/ */
private String serialNo; 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; 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.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 cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.List;
/** /**
* 短信客户端的抽象类提供模板方法减少子类的冗余代码 * 短信客户端的抽象类提供模板方法减少子类的冗余代码
* *
@ -25,14 +17,9 @@ public abstract class AbstractSmsClient implements SmsClient {
* 短信渠道配置 * 短信渠道配置
*/ */
protected volatile SmsChannelProperties properties; protected volatile SmsChannelProperties properties;
/**
* 错误码枚举类
*/
protected final SmsCodeMapping codeMapping;
public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) { public AbstractSmsClient(SmsChannelProperties properties) {
this.properties = prepareProperties(properties); this.properties = properties;
this.codeMapping = codeMapping;
} }
/** /**
@ -54,74 +41,13 @@ public abstract class AbstractSmsClient implements SmsClient {
return; return;
} }
log.info("[refresh][配置({})发生变化,重新初始化]", properties); log.info("[refresh][配置({})发生变化,重新初始化]", properties);
this.properties = prepareProperties(properties);
// 初始化 // 初始化
this.init(); this.init();
} }
/**
* 在赋值给{@link this#properties}子类可根据需要预处理短信渠道配置
*
* @param properties 数据库中存储的短信渠道配置
* @return 满足子类实现的短信渠道配置
*/
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
return properties;
}
@Override @Override
public Long getId() { public Long getId() {
return properties.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; package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
import cn.hutool.core.lang.Assert; 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.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; 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.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient; 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.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient; import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; 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.SendSmsRequest;
import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile; import com.aliyuncs.profile.IClientProfile;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
@ -31,9 +27,8 @@ import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects; 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.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; 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 @Slf4j
public class AliyunSmsClient extends AbstractSmsClient { public class AliyunSmsClient extends AbstractSmsClient {
/**
* 调用成功 code
*/
public static final String API_CODE_SUCCESS = "OK";
/** /**
* REGION, 使用杭州 * REGION, 使用杭州
*/ */
@ -57,7 +57,7 @@ public class AliyunSmsClient extends AbstractSmsClient {
private volatile IAcsClient client; private volatile IAcsClient client;
public AliyunSmsClient(SmsChannelProperties properties) { public AliyunSmsClient(SmsChannelProperties properties) {
super(properties, new AliyunSmsCodeMapping()); super(properties);
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
} }
@ -69,9 +69,9 @@ public class AliyunSmsClient extends AbstractSmsClient {
} }
@Override @Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
String apiTemplateId, List<KeyValue<String, Object>> templateParams) { List<KeyValue<String, Object>> templateParams) throws Throwable {
// 构建参数 // 构建请求
SendSmsRequest request = new SendSmsRequest(); SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(mobile); request.setPhoneNumbers(mobile);
request.setSignName(properties.getSignature()); request.setSignName(properties.getSignature());
@ -79,34 +79,32 @@ public class AliyunSmsClient extends AbstractSmsClient {
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
request.setOutId(String.valueOf(sendLogId)); 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 @Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable { public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
return statuses.stream().map(status -> { return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
SmsReceiveRespDTO resp = new SmsReceiveRespDTO(); .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
resp.setSuccess(status.getSuccess()); .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()); .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
resp.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()));
return resp;
}).collect(Collectors.toList());
} }
@Override @Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
// 构建参数 // 构建请求
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest(); QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
request.setTemplateCode(apiTemplateId); request.setTemplateCode(apiTemplateId);
// 执行请求 // 执行请求
return invoke(request, response -> { QuerySmsTemplateResponse response = client.getAcsResponse(request);
SmsTemplateRespDTO data = new SmsTemplateRespDTO(); if (response.getTemplateStatus() == null) {
data.setId(response.getTemplateCode()).setContent(response.getTemplateContent()); return null;
data.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason()); }
return data; return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
}); .setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
} }
@VisibleForTesting @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但是有统一的 codemessagerequestId 属性所以只好反射
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 芋道源码 * @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.crypto.digest.HmacAlgorithm;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; 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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; 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.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient; 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.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; 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.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
* 基于钉钉 WebHook 实现的调试的短信客户端实现类 * 基于钉钉 WebHook 实现的调试的短信客户端实现类
@ -32,7 +32,7 @@ import java.util.Map;
public class DebugDingTalkSmsClient extends AbstractSmsClient { public class DebugDingTalkSmsClient extends AbstractSmsClient {
public DebugDingTalkSmsClient(SmsChannelProperties properties) { public DebugDingTalkSmsClient(SmsChannelProperties properties) {
super(properties, new DebugDingTalkCodeMapping()); super(properties);
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
} }
@ -42,8 +42,8 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
} }
@Override @Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable { String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
// 构建请求 // 构建请求
String url = buildUrl("robot/send"); String url = buildUrl("robot/send");
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
@ -55,14 +55,15 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params)); String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));
// 解析结果 // 解析结果
Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class); Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
return SmsCommonResult.build(MapUtil.getStr(responseObj, "errcode"), MapUtil.getStr(responseObj, "errorMsg"), String errorCode = MapUtil.getStr(responseObj, "errcode");
null, new SmsSendRespDTO().setSerialNo(StrUtil.uuid()), codeMapping); 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 请求路径 * @param path 请求路径
* @return 请求地址 * @return 请求地址
@ -82,15 +83,14 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
} }
@Override @Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable { public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调"); throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
} }
@Override @Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("") return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(""); .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.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; 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.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; 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.dto.SmsTemplateRespDTO;
@ -17,23 +15,22 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.sms.v20210111.SmsClient; import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.*; import com.tencentcloudapi.sms.v20210111.models.*;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.Objects;
import java.util.function.Supplier;
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.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; 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 * @author shiwp
*/ */
@ -42,7 +39,7 @@ public class TencentSmsClient extends AbstractSmsClient {
/** /**
* 调用成功 code * 调用成功 code
*/ */
public static final String API_SUCCESS_CODE = "Ok"; public static final String API_CODE_SUCCESS = "Ok";
/** /**
* REGION使用南京 * REGION使用南京
@ -51,180 +48,103 @@ public class TencentSmsClient extends AbstractSmsClient {
/** /**
* 是否国际/港澳台短信 * 是否国际/港澳台短信
*
* 0表示国内短信 * 0表示国内短信
* 1表示国际/港澳台短信 * 1表示国际/港澳台短信
*/ */
private static final long INTERNATIONAL = 0L; private static final long INTERNATIONAL_CHINA = 0L;
private SmsClient client; private SmsClient client;
public TencentSmsClient(SmsChannelProperties properties) { public TencentSmsClient(SmsChannelProperties properties) {
super(properties, new TencentSmsCodeMapping()); super(properties);
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
validateSdkAppId(properties);
} }
@Override @Override
protected void doInit() { protected void doInit() {
// 实例化一个认证对象入参需要传入腾讯云账户密钥对 secretIdsecretKey // 实例化一个认证对象入参需要传入腾讯云账户密钥对 secretIdsecretKey
Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret()); Credential credential = new Credential(getApiKey(), properties.getApiSecret());
client = new SmsClient(credential, ENDPOINT); 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 @Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
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) {
SendSmsRequest request = new SendSmsRequest(); SendSmsRequest request = new SendSmsRequest();
request.setSmsSdkAppId(((TencentSmsChannelProperties) properties).getSdkAppId()); request.setSmsSdkAppId(getSdkAppId());
request.setPhoneNumberSet(new String[]{mobile}); request.setPhoneNumberSet(new String[]{mobile});
request.setSignName(properties.getSignature()); request.setSignName(properties.getSignature());
request.setTemplateId(apiTemplateId); request.setTemplateId(apiTemplateId);
request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue()))); request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId))); 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 @Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable { public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class); List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
return CollectionUtils.convertList(callback, status -> { return convertList(callback, status -> new SmsReceiveRespDTO()
SmsReceiveRespDTO data = new SmsReceiveRespDTO(); .setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription()); .setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())
data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus())); .setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())
data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo()); .setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));
SessionContext context;
Long logId;
Assert.notNull(context = status.getSessionContext(), "回执信息中未解析出 context请联系腾讯云小助手");
Assert.notNull(logId = context.getLogId(), "回执信息中未解析出 logId请联系腾讯云小助手");
data.setLogId(logId);
return data;
});
} }
@Override @Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
return invoke(() -> this.buildSmsTemplateStatusRequest(apiTemplateId), // 构建请求
this::doGetSmsTemplate0, DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
response -> { request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]); request.setInternational(INTERNATIONAL_CHINA);
return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping); // 执行请求
}); 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 @VisibleForTesting
SmsTemplateRespDTO convertTemplateStatusDTO(DescribeTemplateListStatus templateStatus) { Integer convertSmsTemplateAuditStatus(int templateStatus) {
if (templateStatus == null) { switch (templateStatus) {
return null; 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 @Data
@ -278,7 +198,7 @@ public class TencentSmsClient extends AbstractSmsClient {
private String serialNo; private String serialNo;
/** /**
* 用户的 session 内容与发送接口的请求参数SessionContext一致 * 用户的 session 内容与发送接口的请求参数 SessionContext 一致
*/ */
@JsonProperty("ext") @JsonProperty("ext")
private SessionContext sessionContext; private SessionContext sessionContext;
@ -293,10 +213,7 @@ public class TencentSmsClient extends AbstractSmsClient {
* 发送短信记录id * 发送短信记录id
*/ */
private Long logId; 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; package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
import cn.hutool.core.util.ReflectUtil; 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.core.KeyValue;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; 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.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.IAcsClient; import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse; import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatcher;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -28,12 +22,10 @@ import org.mockito.Mock;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; 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.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -67,8 +59,7 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
} }
@Test @Test
@SuppressWarnings("unchecked") public void tesSendSms_success() throws Throwable {
public void testDoSendSms() throws ClientException {
// 准备参数 // 准备参数
Long sendLogId = randomLongId(); Long sendLogId = randomLongId();
String mobile = randomString(); String mobile = randomString();
@ -87,20 +78,47 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
}))).thenReturn(response); }))).thenReturn(response);
// 调用 // 调用
SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile, SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
apiTemplateId, templateParams); apiTemplateId, templateParams);
// 断言 // 断言
assertTrue(result.getSuccess());
assertEquals(response.getRequestId(), result.getApiRequestId());
assertEquals(response.getCode(), result.getApiCode()); assertEquals(response.getCode(), result.getApiCode());
assertEquals(response.getMessage(), result.getApiMsg()); assertEquals(response.getMessage(), result.getApiMsg());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); assertEquals(response.getBizId(), result.getSerialNo());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
assertEquals(response.getRequestId(), result.getApiRequestId());
// 断言结果
assertEquals(response.getBizId(), result.getData().getSerialNo());
} }
@Test @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" + String text = "[\n" +
" {\n" + " {\n" +
@ -118,20 +136,21 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
// mock 方法 // mock 方法
// 调用 // 调用
List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text); List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
// 断言 // 断言
assertEquals(1, statuses.size()); assertEquals(1, statuses.size());
assertTrue(statuses.get(0).getSuccess()); assertTrue(statuses.get(0).getSuccess());
assertEquals("DELIVERED", statuses.get(0).getErrorCode()); assertEquals("DELIVERED", statuses.get(0).getErrorCode());
assertEquals("用户接收成功", statuses.get(0).getErrorMsg()); assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
assertEquals("13900000001", statuses.get(0).getMobile()); 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("12345", statuses.get(0).getSerialNo());
assertEquals(67890L, statuses.get(0).getLogId()); assertEquals(67890L, statuses.get(0).getLogId());
} }
@Test @Test
public void testDoGetSmsTemplate() throws ClientException { public void testGetSmsTemplate() throws Throwable {
// 准备参数 // 准备参数
String apiTemplateId = randomString(); String apiTemplateId = randomString();
// mock 方法 // mock 方法
@ -145,18 +164,12 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
}))).thenReturn(response); }))).thenReturn(response);
// 调用 // 调用
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId); SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
// 断言 // 断言
assertEquals(response.getCode(), result.getApiCode()); assertEquals(response.getTemplateCode(), result.getId());
assertEquals(response.getMessage(), result.getApiMsg()); assertEquals(response.getTemplateContent(), result.getContent());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); assertEquals(response.getReason(), result.getAuditReason());
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());
} }
@Test @Test
@ -171,55 +184,4 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
"未知审核状态(3)"); "未知审核状态(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; package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
import cn.hutool.core.util.ReflectUtil; 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.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.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; 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.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.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.*; 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.ArgumentMatchers.argThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -78,7 +74,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
} }
@Test @Test
public void testDoSendSms() throws Throwable { public void testDoSendSms_success() throws Throwable {
// 准备参数 // 准备参数
Long sendLogId = randomLongId(); Long sendLogId = randomLongId();
String mobile = randomString(); String mobile = randomString();
@ -94,7 +90,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
o.setSendStatusSet(sendStatuses); o.setSendStatusSet(sendStatuses);
SendStatus sendStatus = new SendStatus(); SendStatus sendStatus = new SendStatus();
sendStatuses[0] = sendStatus; sendStatuses[0] = sendStatus;
sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE); sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
sendStatus.setMessage("send success"); sendStatus.setMessage("send success");
sendStatus.setSerialNo(serialNo); sendStatus.setSerialNo(serialNo);
}); });
@ -109,20 +105,58 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
}))).thenReturn(response); }))).thenReturn(response);
// 调用 // 调用
SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile, SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
apiTemplateId, templateParams);
// 断言 // 断言
assertTrue(result.getSuccess());
assertEquals(response.getRequestId(), result.getApiRequestId());
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
assertEquals(response.getRequestId(), result.getApiRequestId());
// 断言结果
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getData().getSerialNo());
} }
@Test @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" + String text = "[\n" +
" {\n" + " {\n" +
@ -139,7 +173,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
// mock 方法 // mock 方法
// 调用 // 调用
List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text); List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
// 断言 // 断言
assertEquals(1, statuses.size()); assertEquals(1, statuses.size());
assertTrue(statuses.get(0).getSuccess()); assertTrue(statuses.get(0).getSuccess());
@ -152,7 +186,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
} }
@Test @Test
public void testDoGetSmsTemplate() throws Throwable { public void testGetSmsTemplate() throws Throwable {
// 准备参数 // 准备参数
Long apiTemplateId = randomLongId(); Long apiTemplateId = randomLongId();
String requestId = randomString(); String requestId = randomString();
@ -173,50 +207,24 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
}))).thenReturn(response); }))).thenReturn(response);
// 调用 // 调用
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString()); SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
// 断言 // 断言
assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode()); assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
assertNull(result.getApiMsg()); assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
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());
} }
@Test @Test
public void testConvertSuccessTemplateStatus() { public void testConvertSmsTemplateAuditStatus() {
testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L); assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
} smsClient.convertSmsTemplateAuditStatus(0));
assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
@Test smsClient.convertSmsTemplateAuditStatus(1));
public void testConvertCheckingTemplateStatus() { assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L); smsClient.convertSmsTemplateAuditStatus(-1));
} assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),
"未知审核状态(3)");
@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());
} }
} }

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 ========== // ========== 短信模板 1-002-012-000 ==========
ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(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_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 ========== // ========== 短信发送 1-002-013-000 ==========
ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(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("发送时间") @ExcelProperty("发送时间")
private LocalDateTime sendTime; private LocalDateTime sendTime;
@ExcelProperty("发送结果的编码")
private Integer sendCode;
@ExcelProperty("发送结果的提示")
private String sendMsg;
@ExcelProperty("短信 API 发送结果的编码") @ExcelProperty("短信 API 发送结果的编码")
private String apiSendCode; private String apiSendCode;

View File

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

View File

@ -52,12 +52,6 @@ public class SmsLogRespVO {
@Schema(description = "发送时间") @Schema(description = "发送时间")
private LocalDateTime sendTime; private LocalDateTime sendTime;
@Schema(description = "发送结果的编码", example = "0")
private Integer sendCode;
@Schema(description = "发送结果的提示", example = "成功")
private String sendMsg;
@Schema(description = "短信 API 发送结果的编码", example = "SUCCESS") @Schema(description = "短信 API 发送结果的编码", example = "SUCCESS")
private String apiSendCode; private String apiSendCode;

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.module.system.dal.dataobject.sms; 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.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 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.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@ -115,19 +114,6 @@ public class SmsLogDO extends BaseDO {
* 发送时间 * 发送时间
*/ */
private LocalDateTime sendTime; private LocalDateTime sendTime;
/**
* 发送结果的编码
*
* 枚举 {@link SmsFrameworkErrorCodeConstants}
*/
private Integer sendCode;
/**
* 发送结果的提示
*
* 一般情况下使用 {@link SmsFrameworkErrorCodeConstants}
* 异常情况下通过格式化 Exception 的提示存储
*/
private String sendMsg;
/** /**
* 短信 API 发送结果的编码 * 短信 API 发送结果的编码
* *

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.system.service.sms; 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.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO; 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.SmsLogDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -37,15 +37,15 @@ public interface SmsLogService {
* 更新日志的发送结果 * 更新日志的发送结果
* *
* @param id 日志编号 * @param id 日志编号
* @param sendCode 发送结果的编码 * @param success 发送是否成功
* @param sendMsg 发送结果的提示
* @param apiSendCode 短信 API 发送结果的编码 * @param apiSendCode 短信 API 发送结果的编码
* @param apiSendMsg 短信 API 发送失败的提示 * @param apiSendMsg 短信 API 发送失败的提示
* @param apiRequestId 短信 API 发送返回的唯一请求 ID * @param apiRequestId 短信 API 发送返回的唯一请求 ID
* @param apiSerialNo 短信 API 发送返回的序号 * @param apiSerialNo 短信 API 发送返回的序号
*/ */
void updateSmsSendResult(Long id, Integer sendCode, String sendMsg, void updateSmsSendResult(Long id, Boolean success,
String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo); String apiSendCode, String apiSendMsg,
String apiRequestId, String apiSerialNo);
/** /**
* 更新日志的接收结果 * 更新日志的接收结果
@ -56,7 +56,8 @@ public interface SmsLogService {
* @param apiReceiveCode API 接收结果的编码 * @param apiReceiveCode API 接收结果的编码
* @param apiReceiveMsg 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; 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.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO; 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.SmsLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsLogMapper; 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.SmsReceiveStatusEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -55,13 +54,12 @@ public class SmsLogServiceImpl implements SmsLogService {
} }
@Override @Override
public void updateSmsSendResult(Long id, Integer sendCode, String sendMsg, public void updateSmsSendResult(Long id, Boolean success,
String apiSendCode, String apiSendMsg, String apiSendCode, String apiSendMsg,
String apiRequestId, String apiSerialNo) { String apiRequestId, String apiSerialNo) {
SmsSendStatusEnum sendStatus = CommonResult.isSuccess(sendCode) ? SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE; smsLogMapper.updateById(SmsLogDO.builder().id(id)
smsLogMapper.updateById(SmsLogDO.builder().id(id).sendStatus(sendStatus.getStatus()) .sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now())
.sendTime(LocalDateTime.now()).sendCode(sendCode).sendMsg(sendMsg)
.apiSendCode(apiSendCode).apiSendMsg(apiSendMsg) .apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)
.apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build()); .apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.sms; package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; 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.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; 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.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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; 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.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -35,6 +36,7 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
* @author 芋道源码 * @author 芋道源码
*/ */
@Service @Service
@Slf4j
public class SmsSendServiceImpl implements SmsSendService { public class SmsSendServiceImpl implements SmsSendService {
@Resource @Resource
@ -158,11 +160,17 @@ public class SmsSendServiceImpl implements SmsSendService {
SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId()); SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId()); Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
// 发送短信 // 发送短信
SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(), try {
message.getApiTemplateId(), message.getTemplateParams()); SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),
smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getMsg(), message.getApiTemplateId(), message.getTemplateParams());
sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(), smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
sendResult.getData() != null ? sendResult.getData().getSerialNo() : null); 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 @Override

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.system.service.sms; 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.SmsTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO; 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.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateUpdateReqVO; 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.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.system.service.sms; 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.ReUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.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.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.SmsTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO; 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.SmsTemplatePageReqVO;
@ -21,11 +23,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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); SmsClient smsClient = smsChannelService.getSmsClient(channelId);
Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId)); Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
SmsCommonResult<SmsTemplateRespDTO> templateResult = smsClient.getSmsTemplate(apiTemplateId); SmsTemplateRespDTO template;
// 校验短信模板是否正确 try {
templateResult.checkError(); 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 @Override

View File

@ -164,31 +164,31 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
@Test @Test
public void testGetSmsChannelPage() { public void testGetSmsChannelPage() {
// mock 数据 // mock 数据
SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class, o -> { // 等会查询到 SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class, o -> { // 等会查询到
o.setSignature("芋道源码"); o.setSignature("芋道源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus()); o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(buildTime(2020, 12, 12)); o.setCreateTime(buildTime(2020, 12, 12));
}); });
smsChannelMapper.insert(dbSmsChannel); smsChannelMapper.insert(dbSmsChannel);
// 测试 signature 不匹配 // 测试 signature 不匹配
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setSignature("源码"))); smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setSignature("源码")));
// 测试 status 不匹配 // 测试 status 不匹配
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配 // 测试 createTime 不匹配
smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11)))); smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11))));
// 准备参数 // 准备参数
SmsChannelPageReqVO reqVO = new SmsChannelPageReqVO(); SmsChannelPageReqVO reqVO = new SmsChannelPageReqVO();
reqVO.setSignature("芋道"); reqVO.setSignature("芋道");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24)); reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));
// 调用 // 调用
PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO); PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO);
// 断言 // 断言
assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size()); assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbSmsChannel, pageResult.getList().get(0)); assertPojoEquals(dbSmsChannel, pageResult.getList().get(0));
} }
@Test @Test

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
@ -172,22 +171,20 @@ public class SmsLogServiceImplTest extends BaseDbUnitTest {
smsLogMapper.insert(dbSmsLog); smsLogMapper.insert(dbSmsLog);
// 准备参数 // 准备参数
Long id = dbSmsLog.getId(); Long id = dbSmsLog.getId();
Integer sendCode = randomInteger(); Boolean success = randomBoolean();
String sendMsg = randomString();
String apiSendCode = randomString(); String apiSendCode = randomString();
String apiSendMsg = randomString(); String apiSendMsg = randomString();
String apiRequestId = randomString(); String apiRequestId = randomString();
String apiSerialNo = randomString(); String apiSerialNo = randomString();
// 调用 // 调用
smsLogService.updateSmsSendResult(id, sendCode, sendMsg, smsLogService.updateSmsSendResult(id, success,
apiSendCode, apiSendMsg, apiRequestId, apiSerialNo); apiSendCode, apiSendMsg, apiRequestId, apiSerialNo);
// 断言 // 断言
dbSmsLog = smsLogMapper.selectById(id); dbSmsLog = smsLogMapper.selectById(id);
assertEquals(CommonResult.isSuccess(sendCode) ? SmsSendStatusEnum.SUCCESS.getStatus() assertEquals(success ? SmsSendStatusEnum.SUCCESS.getStatus() : SmsSendStatusEnum.FAILURE.getStatus(),
: SmsSendStatusEnum.FAILURE.getStatus(), dbSmsLog.getSendStatus()); dbSmsLog.getSendStatus());
assertNotNull(dbSmsLog.getSendTime()); assertNotNull(dbSmsLog.getSendTime());
assertEquals(sendMsg, dbSmsLog.getSendMsg());
assertEquals(apiSendCode, dbSmsLog.getApiSendCode()); assertEquals(apiSendCode, dbSmsLog.getApiSendCode());
assertEquals(apiSendMsg, dbSmsLog.getApiSendMsg()); assertEquals(apiSendMsg, dbSmsLog.getApiSendMsg());
assertEquals(apiRequestId, dbSmsLog.getApiRequestId()); 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.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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.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.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
@ -244,15 +243,14 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testDoSendSms() { public void testDoSendSms() throws Throwable {
// 准备参数 // 准备参数
SmsSendMessage message = randomPojo(SmsSendMessage.class); SmsSendMessage message = randomPojo(SmsSendMessage.class);
// mock SmsClientFactory 的方法 // mock SmsClientFactory 的方法
SmsClient smsClient = spy(SmsClient.class); SmsClient smsClient = spy(SmsClient.class);
when(smsChannelService.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient); when(smsChannelService.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient);
// mock SmsClient 的方法 // mock SmsClient 的方法
SmsCommonResult<SmsSendRespDTO> sendResult = randomPojo(SmsCommonResult.class, SmsSendRespDTO.class); SmsSendRespDTO sendResult = randomPojo(SmsSendRespDTO.class);
sendResult.setData(randomPojo(SmsSendRespDTO.class));
when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()), when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()),
eq(message.getTemplateParams()))).thenReturn(sendResult); eq(message.getTemplateParams()))).thenReturn(sendResult);
@ -260,8 +258,8 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
smsService.doSendSms(message); smsService.doSendSms(message);
// 断言 // 断言
verify(smsLogService).updateSmsSendResult(eq(message.getLogId()), verify(smsLogService).updateSmsSendResult(eq(message.getLogId()),
eq(sendResult.getCode()), eq(sendResult.getMsg()), eq(sendResult.getApiCode()), eq(sendResult.getSuccess()), eq(sendResult.getApiCode()),
eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getData().getSerialNo())); eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo()));
} }
@Test @Test

View File

@ -1,13 +1,12 @@
package cn.iocoder.yudao.module.system.service.sms; package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 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.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.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.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.SmsTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
@ -65,7 +64,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testCreateSmsTemplate_success() { public void testCreateSmsTemplate_success() throws Throwable {
// 准备参数 // 准备参数
SmsTemplateCreateReqVO reqVO = randomPojo(SmsTemplateCreateReqVO.class, o -> { SmsTemplateCreateReqVO reqVO = randomPojo(SmsTemplateCreateReqVO.class, o -> {
o.setContent("正在进行登录操作{operation},您的验证码是{code}"); o.setContent("正在进行登录操作{operation},您的验证码是{code}");
@ -80,8 +79,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO); when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
// mock 获得 API 短信模板成功 // mock 获得 API 短信模板成功
when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class, when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(
o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode()))); randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));
// 调用 // 调用
Long smsTemplateId = smsTemplateService.createSmsTemplate(reqVO); Long smsTemplateId = smsTemplateService.createSmsTemplate(reqVO);
@ -96,7 +95,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testUpdateSmsTemplate_success() { public void testUpdateSmsTemplate_success() throws Throwable {
// mock 数据 // mock 数据
SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO(); SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据 smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
@ -115,8 +114,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO); when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
// mock 获得 API 短信模板成功 // mock 获得 API 短信模板成功
when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class, when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(
o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode()))); randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));
// 调用 // 调用
smsTemplateService.updateSmsTemplate(reqVO); smsTemplateService.updateSmsTemplate(reqVO);