Explorar o código

增加钉钉模拟短信的 smsClient 实现类

YunaiV %!s(int64=4) %!d(string=hai) anos
pai
achega
be8b892bf6

+ 4 - 25
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()));
-//    }
-
 }

+ 23 - 0
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;
+    }
+
+}

+ 96 - 0
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<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
+                                                        String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
+        // 构建请求
+        String url = buildUrl("robot/send");
+        Map<String, Object> 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&timestamp=%d&sign=%s",
+                path, properties.getApiKey(), timestamp, sign);
+    }
+
+    @Override
+    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
+    }
+
+    @Override
+    protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
+        SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
+                .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
+        return SmsCommonResult.build("0", "success", null, data, codeMapping);
+    }
+
+}

+ 1 - 0
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", "腾讯云"),

+ 45 - 0
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<KeyValue<String, Object>> templateParams = new ArrayList<>();
+        templateParams.add(new KeyValue<>("code", "1024"));
+        templateParams.add(new KeyValue<>("operation", "嘿嘿"));
+//        SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
+        SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
+        System.out.println(result);
+    }
+
+}