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 31 protected final SmsCodeMapping codeMapping;
32 32  
33 33 public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
34   - this.properties = properties;
  34 + this.properties = prepareProperties(properties);
35 35 this.codeMapping = codeMapping;
36 36 }
37 37  
... ... @@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient {
54 54 return;
55 55 }
56 56 log.info("[refresh][配置({})发生变化,重新初始化]", properties);
57   - this.properties = properties;
  57 + this.properties = prepareProperties(properties);
58 58 // 初始化
59 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 72 @Override
63 73 public Long getId() {
64 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 14 import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
15 15 import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
16 16 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
  17 +import cn.iocoder.yudao.framework.sms.core.property.TencentSmsChannelProperties;
17 18 import com.fasterxml.jackson.annotation.JsonFormat;
18 19 import com.fasterxml.jackson.annotation.JsonProperty;
19 20 import com.google.common.annotations.VisibleForTesting;
... ... @@ -42,21 +43,33 @@ public class TencentSmsClient extends AbstractSmsClient {
42 43  
43 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 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 65 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
50 66 }
51 67  
52 68 @Override
53 69 protected void doInit() {
54   - // init 或者 refresh 时,需要重新封装 properties
55   - properties = TencentSmsChannelProperties.build(properties);
56 70 // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
57 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 75 @Override
... ... @@ -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 104 * 调用腾讯云 SDK 发送短信
78 105 *
... ... @@ -113,7 +140,7 @@ public class TencentSmsClient extends AbstractSmsClient {
113 140 return CollectionUtils.convertList(callback, status -> {
114 141 SmsReceiveRespDTO data = new SmsReceiveRespDTO();
115 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 144 data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
118 145 SessionContext context;
119 146 Long logId;
... ... @@ -130,7 +157,7 @@ public class TencentSmsClient extends AbstractSmsClient {
130 157 this::doGetSmsTemplate0,
131 158 response -> {
132 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 198 DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
172 199 request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
173 200 // 地区 0:表示国内短信。1:表示国际/港澳台短信。
174   - // TODO @FinallySays:那把 0L 枚举下到这个类的静态变量里哈。
175   - request.setInternational(0L);
  201 + request.setInternational(INTERNATIONAL);
176 202 return request;
177 203 }
178 204  
... ... @@ -207,6 +233,11 @@ public class TencentSmsClient extends AbstractSmsClient {
207 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 243 @JsonProperty("user_receive_time")
... ... @@ -270,27 +301,4 @@ public class TencentSmsClient extends AbstractSmsClient {
270 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 19 @Override
20 20 public ErrorCode apply(String apiCode) {
21 21 switch (apiCode) {
22   - case "Ok": return GlobalErrorCodeConstants.SUCCESS;
  22 + case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
23 23 case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
24 24 case "FailedOperation.JsonParseFail":
25 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 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 65 }
66 66  
67 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 81 public void testDoSendSms() throws Throwable {
69 82 // 准备参数
70 83 Long sendLogId = randomLongId();
... ... @@ -81,7 +94,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
81 94 o.setSendStatusSet(sendStatuses);
82 95 SendStatus sendStatus = new SendStatus();
83 96 sendStatuses[0] = sendStatus;
84   - sendStatus.setCode("Ok");
  97 + sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
85 98 sendStatus.setMessage("send success");
86 99 sendStatus.setSerialNo(serialNo);
87 100 });
... ... @@ -162,7 +175,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
162 175 // 调用
163 176 SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
164 177 // 断言
165   - assertEquals("Ok", result.getApiCode());
  178 + assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
166 179 assertNull(result.getApiMsg());
167 180 assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
168 181 assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
... ... @@ -174,12 +187,23 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
174 187 assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
175 188 }
176 189  
177   - // TODO @FinallySays:这个单测,按道理说应该是写成 4 个方法,每个对应一种情况。
178 190 @Test
179   - public void testConvertTemplateStatusDTO() {
  191 + public void testConvertSuccessTemplateStatus() {
180 192 testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
  193 + }
  194 +
  195 + @Test
  196 + public void testConvertCheckingTemplateStatus() {
181 197 testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
  198 + }
  199 +
  200 + @Test
  201 + public void testConvertFailTemplateStatus() {
182 202 testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
  203 + }
  204 +
  205 + @Test
  206 + public void testConvertUnknownTemplateStatus() {
183 207 DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
184 208 templateStatus.setStatusCode(3L);
185 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 20  
21 21 @Test
22 22 public void testApply() {
23   - assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("Ok"));
  23 + assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
24 24 assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
25 25 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
26 26 assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));
... ...