mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 02:08:43 +08:00 
			
		
		
		
	magic 参数处理&AbstractSmsClient模版优化
This commit is contained in:
		| @@ -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(); | ||||||
|   | |||||||
| @@ -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); |  | ||||||
|         // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey |         // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey | ||||||
|         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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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": | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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")); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 FinallySays
					FinallySays