From be8b892bf63899d131f52a0f04d487f65a768dca Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 13 Apr 2021 00:36:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=92=89=E9=92=89=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E7=9F=AD=E4=BF=A1=E7=9A=84=20smsClient=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/impl/SmsClientFactoryImpl.java | 29 +----- .../impl/debug/DebugDingTalkCodeMapping.java | 23 +++++ .../impl/debug/DebugDingTalkSmsClient.java | 96 +++++++++++++++++++ .../sms/core/enums/SmsChannelEnum.java | 1 + ...DebugDingTalkSmsClientIntegrationTest.java | 45 +++++++++ 5 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java create mode 100644 src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java index 35961fa8f..44f87d7df 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.framework.sms.core.client.impl; import cn.iocoder.dashboard.framework.sms.core.client.SmsClient; import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; import cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun.AliyunSmsClient; +import cn.iocoder.dashboard.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient; import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient; import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum; import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; @@ -77,35 +78,13 @@ public class SmsClientFactoryImpl implements SmsClientFactory { Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum)); // 创建客户端 switch (channelEnum) { - case ALIYUN: - return new AliyunSmsClient(properties); - case YUN_PIAN: - return new YunpianSmsClient(properties); + case ALIYUN: return new AliyunSmsClient(properties); + case YUN_PIAN: return new YunpianSmsClient(properties); + case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties)); } -// /** -// * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId -// * -// * @param callbackRequest 短信发送回调函数请求 -// * @return 第三方平台短信唯一标识 -// */ -// public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) { -// for (Long channelId : clients.keySet()) { -// AbstractSmsClient smsClient = clients.get(channelId); -// try { -// SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest); -// if (smsSendResult != null) { -// return smsSendResult; -// } -// } catch (Exception ignored) { -// } -// } -// throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: " -// + JsonUtils.toJsonString(callbackRequest.getParameterMap())); -// } - } diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java new file mode 100644 index 000000000..a2aafb7c0 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java @@ -0,0 +1,23 @@ +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug; + +import cn.iocoder.dashboard.common.exception.ErrorCode; +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping; + +import java.util.Objects; + +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.SMS_UNKNOWN; + +/** + * 钉钉的 SmsCodeMapping 实现类 + * + * @author 芋道源码 + */ +public class DebugDingTalkCodeMapping implements SmsCodeMapping { + + @Override + public ErrorCode apply(String apiCode) { + return Objects.equals(apiCode, "0") ? GlobalErrorCodeConstants.SUCCESS : SMS_UNKNOWN; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java new file mode 100644 index 000000000..9215959b7 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java @@ -0,0 +1,96 @@ +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.http.HttpUtil; +import cn.iocoder.dashboard.common.core.KeyValue; +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult; +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient; +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; +import cn.iocoder.dashboard.util.collection.MapUtils; +import cn.iocoder.dashboard.util.json.JsonUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 基于钉钉 WebHook 实现的调试的短信客户端实现类 + * + * 考虑到省钱,我们使用钉钉 WebHook 模拟发送短信,方便调试。 + * + * @author 芋道源码 + */ +public class DebugDingTalkSmsClient extends AbstractSmsClient { + + public DebugDingTalkSmsClient(SmsChannelProperties properties) { + super(properties, new DebugDingTalkCodeMapping()); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + @Override + protected void doInit() { + } + + @Override + protected SmsCommonResult doSendSms(Long sendLogId, String mobile, + String apiTemplateId, List> templateParams) throws Throwable { + // 构建请求 + String url = buildUrl("robot/send"); + Map params = new HashMap<>(); + params.put("msgtype", "text"); + String content = String.format("【模拟短信】\n手机号:%s\n短信日志编号:%d\n模板参数:%s", + mobile, sendLogId, MapUtils.convertMap(templateParams)); + params.put("text", MapUtil.builder().put("content", content).build()); + // 执行请求 + String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params)); + // 解析结果 + Map responseObj = JsonUtils.parseObject(responseText, Map.class); + return SmsCommonResult.build(MapUtil.getStr(responseObj, "errcode"), MapUtil.getStr(responseObj, "errorMsg"), + null, new SmsSendRespDTO().setSerialNo(StrUtil.uuid()), codeMapping); + } + + /** + * 构建请求地址 + * + * 参见 https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71 文档 + * + * @param path 请求路径 + * @return 请求地址 + */ + @SuppressWarnings("SameParameterValue") + private String buildUrl(String path) { + // 生成 timestamp + long timestamp = System.currentTimeMillis(); + // 生成 sign + String secret = properties.getApiSecret(); + String stringToSign = timestamp + "\n" + secret; + byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign); + String sign = Base64.encode(signData); + // 构建最终 URL + return String.format("https://oapi.dingtalk.com/%s?access_token=%s×tamp=%d&sign=%s", + path, properties.getApiKey(), timestamp, sign); + } + + @Override + protected List doParseSmsReceiveStatus(String text) throws Throwable { + throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调"); + } + + @Override + protected SmsCommonResult doGetSmsTemplate(String apiTemplateId) { + SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("") + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(""); + return SmsCommonResult.build("0", "success", null, data, codeMapping); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java b/src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java index f2e675198..ba2615d3d 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java +++ b/src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java @@ -14,6 +14,7 @@ import lombok.Getter; @AllArgsConstructor public enum SmsChannelEnum { + DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"), YUN_PIAN("YUN_PIAN", "云片"), ALIYUN("ALIYUN", "阿里云"), // TENCENT("TENCENT", "腾讯云"), diff --git a/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java new file mode 100644 index 000000000..5815aa75c --- /dev/null +++ b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java @@ -0,0 +1,45 @@ +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug; + +import cn.iocoder.dashboard.common.core.KeyValue; +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult; +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum; +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link DebugDingTalkSmsClient} 的集成测试 + */ +public class DebugDingTalkSmsClientIntegrationTest { + + private static DebugDingTalkSmsClient smsClient; + + @BeforeAll + public static void init() { + // 创建配置类 + SmsChannelProperties properties = new SmsChannelProperties(); + properties.setId(1L); + properties.setSignature("芋道"); + properties.setCode(SmsChannelEnum.DEBUG_DING_TALK.getCode()); + properties.setApiKey("696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859"); + properties.setApiSecret("SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67"); + // 创建客户端 + smsClient = new DebugDingTalkSmsClient(properties); + smsClient.init(); + } + + @Test + public void testSendSms() { + List> templateParams = new ArrayList<>(); + templateParams.add(new KeyValue<>("code", "1024")); + templateParams.add(new KeyValue<>("operation", "嘿嘿")); +// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams); + SmsCommonResult result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams); + System.out.println(result); + } + +}