Commit eb147a92ff44eb9532d66297508668cefd1b0b3b

Authored by FinallySays
1 parent 2e668455

magic 参数处理&AbstractSmsClient模版优化

yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java
@@ -31,7 +31,7 @@ public abstract class AbstractSmsClient implements SmsClient { @@ -31,7 +31,7 @@ public abstract class AbstractSmsClient implements SmsClient {
31 protected final SmsCodeMapping codeMapping; 31 protected final SmsCodeMapping codeMapping;
32 32
33 public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) { 33 public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
34 - this.properties = properties; 34 + this.properties = prepareProperties(properties);
35 this.codeMapping = codeMapping; 35 this.codeMapping = codeMapping;
36 } 36 }
37 37
@@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient { @@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient {
54 return; 54 return;
55 } 55 }
56 log.info("[refresh][配置({})发生变化,重新初始化]", properties); 56 log.info("[refresh][配置({})发生变化,重新初始化]", properties);
57 - this.properties = properties; 57 + this.properties = prepareProperties(properties);
58 // 初始化 58 // 初始化
59 this.init(); 59 this.init();
60 } 60 }
61 61
  62 + /**
  63 + * 在赋值给{@link this#properties}前,子类可根据需要预处理短信渠道配置
  64 + *
  65 + * @param properties 数据库中存储的短信渠道配置
  66 + * @return 满足子类实现的短信渠道配置
  67 + */
  68 + protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
  69 + return properties;
  70 + }
  71 +
62 @Override 72 @Override
63 public Long getId() { 73 public Long getId() {
64 return properties.getId(); 74 return properties.getId();
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO; @@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
14 import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient; 14 import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
15 import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum; 15 import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
16 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; 16 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
  17 +import cn.iocoder.yudao.framework.sms.core.property.TencentSmsChannelProperties;
17 import com.fasterxml.jackson.annotation.JsonFormat; 18 import com.fasterxml.jackson.annotation.JsonFormat;
18 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
19 import com.google.common.annotations.VisibleForTesting; 20 import com.google.common.annotations.VisibleForTesting;
@@ -42,21 +43,33 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -42,21 +43,33 @@ public class TencentSmsClient extends AbstractSmsClient {
42 43
43 private SmsClient client; 44 private SmsClient client;
44 45
  46 + /**
  47 + * 调用成功 code
  48 + */
  49 + public static final String API_SUCCESS_CODE = "Ok";
  50 +
  51 + /**
  52 + * REGION, 使用南京
  53 + */
  54 + private static final String ENDPOINT = "ap-nanjing";
  55 +
  56 + /**
  57 + * 是否国际/港澳台短信:
  58 + * 0:表示国内短信。
  59 + * 1:表示国际/港澳台短信。
  60 + */
  61 + private static final long INTERNATIONAL = 0L;
  62 +
45 public TencentSmsClient(SmsChannelProperties properties) { 63 public TencentSmsClient(SmsChannelProperties properties) {
46 - // 腾讯云发放短信的时候,需要额外的参数 sdkAppId。考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。  
47 - // 因此,这边需要使用 TencentSmsChannelProperties 做拆分,重新封装到 properties 内。  
48 - super(TencentSmsChannelProperties.build(properties), new TencentSmsCodeMapping()); 64 + super(properties, new TencentSmsCodeMapping());
49 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); 65 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
50 } 66 }
51 67
52 @Override 68 @Override
53 protected void doInit() { 69 protected void doInit() {
54 - // init 或者 refresh 时,需要重新封装 properties  
55 - properties = TencentSmsChannelProperties.build(properties);  
56 // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey 70 // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
57 Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret()); 71 Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
58 - // TODO @FinallySays:那把 ap-nanjing 枚举下到这个类的静态变量里哈。  
59 - client = new SmsClient(credential, "ap-nanjing"); 72 + client = new SmsClient(credential, ENDPOINT);
60 } 73 }
61 74
62 @Override 75 @Override
@@ -73,6 +86,20 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -73,6 +86,20 @@ public class TencentSmsClient extends AbstractSmsClient {
73 }); 86 });
74 } 87 }
75 88
  89 +
  90 + /**
  91 + * 腾讯云发放短信的时候,需要额外的参数 sdkAppId。
  92 + * 考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
  93 + * 因此,这边需要使用 TencentSmsChannelProperties 做拆分,重新封装到 properties 内。
  94 + *
  95 + * @param properties 数据库中存储的短信渠道配置
  96 + * @return TencentSmsChannelProperties
  97 + */
  98 + @Override
  99 + protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
  100 + return TencentSmsChannelProperties.build(properties);
  101 + }
  102 +
76 /** 103 /**
77 * 调用腾讯云 SDK 发送短信 104 * 调用腾讯云 SDK 发送短信
78 * 105 *
@@ -113,7 +140,7 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -113,7 +140,7 @@ public class TencentSmsClient extends AbstractSmsClient {
113 return CollectionUtils.convertList(callback, status -> { 140 return CollectionUtils.convertList(callback, status -> {
114 SmsReceiveRespDTO data = new SmsReceiveRespDTO(); 141 SmsReceiveRespDTO data = new SmsReceiveRespDTO();
115 data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription()); 142 data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription());
116 - data.setReceiveTime(status.getReceiveTime()).setSuccess("SUCCESS".equalsIgnoreCase(status.getStatus())); 143 + data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()));
117 data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo()); 144 data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
118 SessionContext context; 145 SessionContext context;
119 Long logId; 146 Long logId;
@@ -130,7 +157,7 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -130,7 +157,7 @@ public class TencentSmsClient extends AbstractSmsClient {
130 this::doGetSmsTemplate0, 157 this::doGetSmsTemplate0,
131 response -> { 158 response -> {
132 SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]); 159 SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]);
133 - return SmsCommonResult.build("Ok", null, response.getRequestId(), data, codeMapping); 160 + return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping);
134 }); 161 });
135 } 162 }
136 163
@@ -171,8 +198,7 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -171,8 +198,7 @@ public class TencentSmsClient extends AbstractSmsClient {
171 DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest(); 198 DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
172 request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)}); 199 request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
173 // 地区 0:表示国内短信。1:表示国际/港澳台短信。 200 // 地区 0:表示国内短信。1:表示国际/港澳台短信。
174 - // TODO @FinallySays:那把 0L 枚举下到这个类的静态变量里哈。  
175 - request.setInternational(0L); 201 + request.setInternational(INTERNATIONAL);
176 return request; 202 return request;
177 } 203 }
178 204
@@ -207,6 +233,11 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -207,6 +233,11 @@ public class TencentSmsClient extends AbstractSmsClient {
207 private static class SmsReceiveStatus { 233 private static class SmsReceiveStatus {
208 234
209 /** 235 /**
  236 + * 短信接受成功 code
  237 + */
  238 + public static final String SUCCESS_CODE = "SUCCESS";
  239 +
  240 + /**
210 * 用户实际接收到短信的时间 241 * 用户实际接收到短信的时间
211 */ 242 */
212 @JsonProperty("user_receive_time") 243 @JsonProperty("user_receive_time")
@@ -270,27 +301,4 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -270,27 +301,4 @@ public class TencentSmsClient extends AbstractSmsClient {
270 R apply(T t) throws TencentCloudSDKException; 301 R apply(T t) throws TencentCloudSDKException;
271 } 302 }
272 303
273 - // TODO @FinallySays:要不单独一个类,不用作为内部类哈。这样,可能一看就知道,哟,腾讯短信是特殊的  
274 - @Data  
275 - private static class TencentSmsChannelProperties extends SmsChannelProperties {  
276 -  
277 - private String sdkAppId;  
278 -  
279 - public static TencentSmsChannelProperties build(SmsChannelProperties properties) {  
280 - if (properties instanceof TencentSmsChannelProperties) {  
281 - return (TencentSmsChannelProperties) properties;  
282 - }  
283 - TencentSmsChannelProperties result = BeanUtil.toBean(properties, TencentSmsChannelProperties.class);  
284 - String combineKey = properties.getApiKey();  
285 - Assert.notEmpty(combineKey, "apiKey 不能为空");  
286 - String[] keys = combineKey.trim().split(" ");  
287 - Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");  
288 - Assert.notBlank(keys[0], "腾讯云短信 secretId 不能为空");  
289 - Assert.notBlank(keys[1], "腾讯云短信 sdkAppId 不能为空");  
290 - result.setSdkAppId(keys[1]).setApiKey(keys[0]);  
291 - return result;  
292 - }  
293 - }  
294 -  
295 -  
296 } 304 }
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMapping.java
@@ -19,7 +19,7 @@ public class TencentSmsCodeMapping implements SmsCodeMapping { @@ -19,7 +19,7 @@ public class TencentSmsCodeMapping implements SmsCodeMapping {
19 @Override 19 @Override
20 public ErrorCode apply(String apiCode) { 20 public ErrorCode apply(String apiCode) {
21 switch (apiCode) { 21 switch (apiCode) {
22 - case "Ok": return GlobalErrorCodeConstants.SUCCESS; 22 + case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
23 case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID; 23 case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
24 case "FailedOperation.JsonParseFail": 24 case "FailedOperation.JsonParseFail":
25 case "MissingParameter.EmptyPhoneNumberSet": 25 case "MissingParameter.EmptyPhoneNumberSet":
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/property/TencentSmsChannelProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.sms.core.property;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 腾讯云短信配置实现类
  9 + * 腾讯云发送短信时,需要额外的参数 sdkAppId,
  10 + *
  11 + * @author shiwp
  12 + */
  13 +@Data
  14 +
  15 +public class TencentSmsChannelProperties extends SmsChannelProperties {
  16 +
  17 + /**
  18 + * 应用 id
  19 + */
  20 + private String sdkAppId;
  21 +
  22 + /**
  23 + * 考虑到不破坏原有的 apiKey + apiSecret 的结构,
  24 + * 所以腾讯云短信存储时,将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
  25 + * 因此在使用时,需要将 secretId 和 sdkAppId 解析出来,分别存储到对应字段中。
  26 + */
  27 + public static TencentSmsChannelProperties build(SmsChannelProperties properties) {
  28 + if (properties instanceof TencentSmsChannelProperties) {
  29 + return (TencentSmsChannelProperties) properties;
  30 + }
  31 + TencentSmsChannelProperties result = BeanUtil.toBean(properties, TencentSmsChannelProperties.class);
  32 + String combineKey = properties.getApiKey();
  33 + Assert.notEmpty(combineKey, "apiKey 不能为空");
  34 + String[] keys = combineKey.trim().split(" ");
  35 + Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
  36 + Assert.notBlank(keys[0], "腾讯云短信 secretId 不能为空");
  37 + Assert.notBlank(keys[1], "腾讯云短信 sdkAppId 不能为空");
  38 + result.setSdkAppId(keys[1]).setApiKey(keys[0]);
  39 + return result;
  40 + }
  41 +}
0 \ No newline at end of file 42 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java
@@ -65,6 +65,19 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { @@ -65,6 +65,19 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
65 } 65 }
66 66
67 @Test 67 @Test
  68 + public void testRefresh() {
  69 + // 准备参数
  70 + SmsChannelProperties p = new SmsChannelProperties()
  71 + .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
  72 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
  73 + .setSignature("芋道源码");
  74 + // 调用
  75 + smsClient.refresh(p);
  76 + // 断言
  77 + assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
  78 + }
  79 +
  80 + @Test
68 public void testDoSendSms() throws Throwable { 81 public void testDoSendSms() throws Throwable {
69 // 准备参数 82 // 准备参数
70 Long sendLogId = randomLongId(); 83 Long sendLogId = randomLongId();
@@ -81,7 +94,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { @@ -81,7 +94,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
81 o.setSendStatusSet(sendStatuses); 94 o.setSendStatusSet(sendStatuses);
82 SendStatus sendStatus = new SendStatus(); 95 SendStatus sendStatus = new SendStatus();
83 sendStatuses[0] = sendStatus; 96 sendStatuses[0] = sendStatus;
84 - sendStatus.setCode("Ok"); 97 + sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
85 sendStatus.setMessage("send success"); 98 sendStatus.setMessage("send success");
86 sendStatus.setSerialNo(serialNo); 99 sendStatus.setSerialNo(serialNo);
87 }); 100 });
@@ -162,7 +175,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { @@ -162,7 +175,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
162 // 调用 175 // 调用
163 SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString()); 176 SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
164 // 断言 177 // 断言
165 - assertEquals("Ok", result.getApiCode()); 178 + assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
166 assertNull(result.getApiMsg()); 179 assertNull(result.getApiMsg());
167 assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); 180 assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
168 assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); 181 assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
@@ -174,12 +187,23 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { @@ -174,12 +187,23 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
174 assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason()); 187 assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
175 } 188 }
176 189
177 - // TODO @FinallySays:这个单测,按道理说应该是写成 4 个方法,每个对应一种情况。  
178 @Test 190 @Test
179 - public void testConvertTemplateStatusDTO() { 191 + public void testConvertSuccessTemplateStatus() {
180 testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L); 192 testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
  193 + }
  194 +
  195 + @Test
  196 + public void testConvertCheckingTemplateStatus() {
181 testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L); 197 testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
  198 + }
  199 +
  200 + @Test
  201 + public void testConvertFailTemplateStatus() {
182 testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L); 202 testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
  203 + }
  204 +
  205 + @Test
  206 + public void testConvertUnknownTemplateStatus() {
183 DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus(); 207 DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
184 templateStatus.setStatusCode(3L); 208 templateStatus.setStatusCode(3L);
185 Long templateId = randomLongId(); 209 Long templateId = randomLongId();
yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsCodeMappingTest.java
@@ -20,7 +20,7 @@ public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest { @@ -20,7 +20,7 @@ public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest {
20 20
21 @Test 21 @Test
22 public void testApply() { 22 public void testApply() {
23 - assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("Ok")); 23 + assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
24 assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord")); 24 assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
25 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail")); 25 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
26 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet")); 26 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));