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 3 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
5 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 7 import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
7 8 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
8 9 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
... ... @@ -77,35 +78,13 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
77 78 Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
78 79 // 创建客户端
79 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 86 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
87 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 14 @AllArgsConstructor
15 15 public enum SmsChannelEnum {
16 16  
  17 + DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
17 18 YUN_PIAN("YUN_PIAN", "云片"),
18 19 ALIYUN("ALIYUN", "阿里云"),
19 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 +}
... ...