Commit be8b892bf63899d131f52a0f04d487f65a768dca
1 parent
1740128d
增加钉钉模拟短信的 smsClient 实现类
Showing
5 changed files
with
169 additions
and
25 deletions
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×tamp=%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 | +} |