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 | 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×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
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 | +} | ... | ... |