magic 参数处理&AbstractSmsClient模版优化

This commit is contained in:
FinallySays 2022-04-08 11:10:39 +08:00
parent 2e66845584
commit eb147a92ff
6 changed files with 125 additions and 42 deletions

View File

@ -31,7 +31,7 @@ public abstract class AbstractSmsClient implements SmsClient {
protected final SmsCodeMapping codeMapping; protected final SmsCodeMapping codeMapping;
public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) { public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
this.properties = properties; this.properties = prepareProperties(properties);
this.codeMapping = codeMapping; this.codeMapping = codeMapping;
} }
@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient {
return; return;
} }
log.info("[refresh][配置({})发生变化,重新初始化]", properties); log.info("[refresh][配置({})发生变化,重新初始化]", properties);
this.properties = 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();

View File

@ -14,6 +14,7 @@ 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.sms.core.property.TencentSmsChannelProperties;
import com.fasterxml.jackson.annotation.JsonFormat; 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;
@ -42,21 +43,33 @@ public class TencentSmsClient extends AbstractSmsClient {
private SmsClient client; private SmsClient client;
/**
* 调用成功 code
*/
public static final String API_SUCCESS_CODE = "Ok";
/**
* REGION, 使用南京
*/
private static final String ENDPOINT = "ap-nanjing";
/**
* 是否国际/港澳台短信
* 0表示国内短信
* 1表示国际/港澳台短信
*/
private static final long INTERNATIONAL = 0L;
public TencentSmsClient(SmsChannelProperties properties) { public TencentSmsClient(SmsChannelProperties properties) {
// 腾讯云发放短信的时候需要额外的参数 sdkAppId考虑到不破坏原有的 apiKey + apiSecret 的结构所以将 secretId 拼接到 apiKey 字段中格式为 "secretId sdkAppId" super(properties, new TencentSmsCodeMapping());
// 因此这边需要使用 TencentSmsChannelProperties 做拆分重新封装到 properties
super(TencentSmsChannelProperties.build(properties), new TencentSmsCodeMapping());
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
} }
@Override @Override
protected void doInit() { protected void doInit() {
// init 或者 refresh 需要重新封装 properties
properties = TencentSmsChannelProperties.build(properties);
// 实例化一个认证对象入参需要传入腾讯云账户密钥对 secretIdsecretKey // 实例化一个认证对象入参需要传入腾讯云账户密钥对 secretIdsecretKey
Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret()); Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
// TODO @FinallySays那把 ap-nanjing 枚举下到这个类的静态变量里哈 client = new SmsClient(credential, ENDPOINT);
client = new SmsClient(credential, "ap-nanjing");
} }
@Override @Override
@ -73,6 +86,20 @@ public class TencentSmsClient extends AbstractSmsClient {
}); });
} }
/**
* 腾讯云发放短信的时候需要额外的参数 sdkAppId
* 考虑到不破坏原有的 apiKey + apiSecret 的结构所以将 secretId 拼接到 apiKey 字段中格式为 "secretId sdkAppId"
* 因此这边需要使用 TencentSmsChannelProperties 做拆分重新封装到 properties
*
* @param properties 数据库中存储的短信渠道配置
* @return TencentSmsChannelProperties
*/
@Override
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
return TencentSmsChannelProperties.build(properties);
}
/** /**
* 调用腾讯云 SDK 发送短信 * 调用腾讯云 SDK 发送短信
* *
@ -113,7 +140,7 @@ public class TencentSmsClient extends AbstractSmsClient {
return CollectionUtils.convertList(callback, status -> { return CollectionUtils.convertList(callback, status -> {
SmsReceiveRespDTO data = new SmsReceiveRespDTO(); SmsReceiveRespDTO data = new SmsReceiveRespDTO();
data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription()); data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription());
data.setReceiveTime(status.getReceiveTime()).setSuccess("SUCCESS".equalsIgnoreCase(status.getStatus())); data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()));
data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo()); data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
SessionContext context; SessionContext context;
Long logId; Long logId;
@ -130,7 +157,7 @@ public class TencentSmsClient extends AbstractSmsClient {
this::doGetSmsTemplate0, this::doGetSmsTemplate0,
response -> { response -> {
SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]); SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]);
return SmsCommonResult.build("Ok", null, response.getRequestId(), data, codeMapping); return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping);
}); });
} }
@ -171,8 +198,7 @@ public class TencentSmsClient extends AbstractSmsClient {
DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest(); DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)}); request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
// 地区 0表示国内短信1表示国际/港澳台短信 // 地区 0表示国内短信1表示国际/港澳台短信
// TODO @FinallySays那把 0L 枚举下到这个类的静态变量里哈 request.setInternational(INTERNATIONAL);
request.setInternational(0L);
return request; return request;
} }
@ -206,6 +232,11 @@ public class TencentSmsClient extends AbstractSmsClient {
@Data @Data
private static class SmsReceiveStatus { private static class SmsReceiveStatus {
/**
* 短信接受成功 code
*/
public static final String SUCCESS_CODE = "SUCCESS";
/** /**
* 用户实际接收到短信的时间 * 用户实际接收到短信的时间
*/ */
@ -270,27 +301,4 @@ public class TencentSmsClient extends AbstractSmsClient {
R apply(T t) throws TencentCloudSDKException; R apply(T t) throws TencentCloudSDKException;
} }
// TODO @FinallySays要不单独一个类不用作为内部类哈这样可能一看就知道腾讯短信是特殊的
@Data
private static class TencentSmsChannelProperties extends SmsChannelProperties {
private String 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

@ -19,7 +19,7 @@ public class TencentSmsCodeMapping implements SmsCodeMapping {
@Override @Override
public ErrorCode apply(String apiCode) { public ErrorCode apply(String apiCode) {
switch (apiCode) { switch (apiCode) {
case "Ok": return GlobalErrorCodeConstants.SUCCESS; case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID; case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
case "FailedOperation.JsonParseFail": case "FailedOperation.JsonParseFail":
case "MissingParameter.EmptyPhoneNumberSet": case "MissingParameter.EmptyPhoneNumberSet":

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.sms.core.property;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
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

@ -64,6 +64,19 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client")); assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
} }
@Test
public void testRefresh() {
// 准备参数
SmsChannelProperties p = new SmsChannelProperties()
.setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey避免构建报错
.setApiSecret(randomString()) // 随机一个 apiSecret避免构建报错
.setSignature("芋道源码");
// 调用
smsClient.refresh(p);
// 断言
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
}
@Test @Test
public void testDoSendSms() throws Throwable { public void testDoSendSms() throws Throwable {
// 准备参数 // 准备参数
@ -81,7 +94,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("Ok"); sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
sendStatus.setMessage("send success"); sendStatus.setMessage("send success");
sendStatus.setSerialNo(serialNo); sendStatus.setSerialNo(serialNo);
}); });
@ -162,7 +175,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
// 调用 // 调用
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString()); SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
// 断言 // 断言
assertEquals("Ok", result.getApiCode()); assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
assertNull(result.getApiMsg()); assertNull(result.getApiMsg());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
@ -174,12 +187,23 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason()); assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
} }
// TODO @FinallySays这个单测按道理说应该是写成 4 个方法每个对应一种情况
@Test @Test
public void testConvertTemplateStatusDTO() { public void testConvertSuccessTemplateStatus() {
testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L); testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
}
@Test
public void testConvertCheckingTemplateStatus() {
testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L); testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
}
@Test
public void testConvertFailTemplateStatus() {
testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L); testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
}
@Test
public void testConvertUnknownTemplateStatus() {
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus(); DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
templateStatus.setStatusCode(3L); templateStatus.setStatusCode(3L);
Long templateId = randomLongId(); Long templateId = randomLongId();

View File

@ -20,7 +20,7 @@ public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest {
@Test @Test
public void testApply() { public void testApply() {
assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("Ok")); assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord")); assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail")); assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet")); assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));