Commit be8b892bf63899d131f52a0f04d487f65a768dca

Authored by YunaiV
1 parent 1740128d

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

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; @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.framework.sms.core.client.impl;
3 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient; 3 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
4 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory; 4 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
5 import cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun.AliyunSmsClient; 5 import cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
6 import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient; 7 import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
7 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum; 8 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
8 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties; 9 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
@@ -77,35 +78,13 @@ public class SmsClientFactoryImpl implements SmsClientFactory { @@ -77,35 +78,13 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
77 Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum)); 78 Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
78 // 创建客户端 79 // 创建客户端
79 switch (channelEnum) { 80 switch (channelEnum) {
80 - case ALIYUN:  
81 - return new AliyunSmsClient(properties);  
82 - case YUN_PIAN:  
83 - return new YunpianSmsClient(properties); 81 + case ALIYUN: return new AliyunSmsClient(properties);
  82 + case YUN_PIAN: return new YunpianSmsClient(properties);
  83 + case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
84 } 84 }
85 // 创建失败,错误日志 + 抛出异常 85 // 创建失败,错误日志 + 抛出异常
86 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); 86 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
87 throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties)); 87 throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
88 } 88 }
89 89
90 -// /**  
91 -// * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId  
92 -// *  
93 -// * @param callbackRequest 短信发送回调函数请求  
94 -// * @return 第三方平台短信唯一标识  
95 -// */  
96 -// public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {  
97 -// for (Long channelId : clients.keySet()) {  
98 -// AbstractSmsClient smsClient = clients.get(channelId);  
99 -// try {  
100 -// SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);  
101 -// if (smsSendResult != null) {  
102 -// return smsSendResult;  
103 -// }  
104 -// } catch (Exception ignored) {  
105 -// }  
106 -// }  
107 -// throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "  
108 -// + JsonUtils.toJsonString(callbackRequest.getParameterMap()));  
109 -// }  
110 -  
111 } 90 }
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
  6 +
  7 +import java.util.Objects;
  8 +
  9 +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
  10 +
  11 +/**
  12 + * 钉钉的 SmsCodeMapping 实现类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class DebugDingTalkCodeMapping implements SmsCodeMapping {
  17 +
  18 + @Override
  19 + public ErrorCode apply(String apiCode) {
  20 + return Objects.equals(apiCode, "0") ? GlobalErrorCodeConstants.SUCCESS : SMS_UNKNOWN;
  21 + }
  22 +
  23 +}
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.hutool.core.codec.Base64;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.crypto.digest.DigestUtil;
  8 +import cn.hutool.crypto.digest.HmacAlgorithm;
  9 +import cn.hutool.http.HttpUtil;
  10 +import cn.iocoder.dashboard.common.core.KeyValue;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  13 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  14 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  15 +import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
  16 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  17 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  18 +import cn.iocoder.dashboard.util.collection.MapUtils;
  19 +import cn.iocoder.dashboard.util.json.JsonUtils;
  20 +
  21 +import java.util.HashMap;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +/**
  26 + * 基于钉钉 WebHook 实现的调试的短信客户端实现类
  27 + *
  28 + * 考虑到省钱,我们使用钉钉 WebHook 模拟发送短信,方便调试。
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +public class DebugDingTalkSmsClient extends AbstractSmsClient {
  33 +
  34 + public DebugDingTalkSmsClient(SmsChannelProperties properties) {
  35 + super(properties, new DebugDingTalkCodeMapping());
  36 + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
  37 + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
  38 + }
  39 +
  40 + @Override
  41 + protected void doInit() {
  42 + }
  43 +
  44 + @Override
  45 + protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
  46 + String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
  47 + // 构建请求
  48 + String url = buildUrl("robot/send");
  49 + Map<String, Object> params = new HashMap<>();
  50 + params.put("msgtype", "text");
  51 + String content = String.format("【模拟短信】\n手机号:%s\n短信日志编号:%d\n模板参数:%s",
  52 + mobile, sendLogId, MapUtils.convertMap(templateParams));
  53 + params.put("text", MapUtil.builder().put("content", content).build());
  54 + // 执行请求
  55 + String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));
  56 + // 解析结果
  57 + Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
  58 + return SmsCommonResult.build(MapUtil.getStr(responseObj, "errcode"), MapUtil.getStr(responseObj, "errorMsg"),
  59 + null, new SmsSendRespDTO().setSerialNo(StrUtil.uuid()), codeMapping);
  60 + }
  61 +
  62 + /**
  63 + * 构建请求地址
  64 + *
  65 + * 参见 https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71 文档
  66 + *
  67 + * @param path 请求路径
  68 + * @return 请求地址
  69 + */
  70 + @SuppressWarnings("SameParameterValue")
  71 + private String buildUrl(String path) {
  72 + // 生成 timestamp
  73 + long timestamp = System.currentTimeMillis();
  74 + // 生成 sign
  75 + String secret = properties.getApiSecret();
  76 + String stringToSign = timestamp + "\n" + secret;
  77 + byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign);
  78 + String sign = Base64.encode(signData);
  79 + // 构建最终 URL
  80 + return String.format("https://oapi.dingtalk.com/%s?access_token=%s&timestamp=%d&sign=%s",
  81 + path, properties.getApiKey(), timestamp, sign);
  82 + }
  83 +
  84 + @Override
  85 + protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
  86 + throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
  87 + }
  88 +
  89 + @Override
  90 + protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
  91 + SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
  92 + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
  93 + return SmsCommonResult.build("0", "success", null, data, codeMapping);
  94 + }
  95 +
  96 +}
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java
@@ -14,6 +14,7 @@ import lombok.Getter; @@ -14,6 +14,7 @@ import lombok.Getter;
14 @AllArgsConstructor 14 @AllArgsConstructor
15 public enum SmsChannelEnum { 15 public enum SmsChannelEnum {
16 16
  17 + DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
17 YUN_PIAN("YUN_PIAN", "云片"), 18 YUN_PIAN("YUN_PIAN", "云片"),
18 ALIYUN("ALIYUN", "阿里云"), 19 ALIYUN("ALIYUN", "阿里云"),
19 // TENCENT("TENCENT", "腾讯云"), 20 // TENCENT("TENCENT", "腾讯云"),
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  6 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  7 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  8 +import org.junit.jupiter.api.BeforeAll;
  9 +import org.junit.jupiter.api.Test;
  10 +
  11 +import java.util.ArrayList;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * {@link DebugDingTalkSmsClient} 的集成测试
  16 + */
  17 +public class DebugDingTalkSmsClientIntegrationTest {
  18 +
  19 + private static DebugDingTalkSmsClient smsClient;
  20 +
  21 + @BeforeAll
  22 + public static void init() {
  23 + // 创建配置类
  24 + SmsChannelProperties properties = new SmsChannelProperties();
  25 + properties.setId(1L);
  26 + properties.setSignature("芋道");
  27 + properties.setCode(SmsChannelEnum.DEBUG_DING_TALK.getCode());
  28 + properties.setApiKey("696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859");
  29 + properties.setApiSecret("SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67");
  30 + // 创建客户端
  31 + smsClient = new DebugDingTalkSmsClient(properties);
  32 + smsClient.init();
  33 + }
  34 +
  35 + @Test
  36 + public void testSendSms() {
  37 + List<KeyValue<String, Object>> templateParams = new ArrayList<>();
  38 + templateParams.add(new KeyValue<>("code", "1024"));
  39 + templateParams.add(new KeyValue<>("operation", "嘿嘿"));
  40 +// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
  41 + SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
  42 + System.out.println(result);
  43 + }
  44 +
  45 +}