Commit 4f7ac969feec4fc61f403f3aa2e605473900e544

Authored by scholar
1 parent efc5ea23

增加七牛云短信实现

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
@@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap; @@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap;
5 import cn.hutool.core.net.url.UrlBuilder; 5 import cn.hutool.core.net.url.UrlBuilder;
6 import cn.hutool.core.util.ReflectUtil; 6 import cn.hutool.core.util.ReflectUtil;
7 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.http.HttpRequest;
  9 +import cn.hutool.http.HttpResponse;
8 import org.springframework.util.StringUtils; 10 import org.springframework.util.StringUtils;
9 import org.springframework.web.util.UriComponents; 11 import org.springframework.web.util.UriComponents;
10 import org.springframework.web.util.UriComponentsBuilder; 12 import org.springframework.web.util.UriComponentsBuilder;
@@ -109,7 +111,7 @@ public class HttpUtils { @@ -109,7 +111,7 @@ public class HttpUtils {
109 authorization = Base64.decodeStr(authorization); 111 authorization = Base64.decodeStr(authorization);
110 clientId = StrUtil.subBefore(authorization, ":", false); 112 clientId = StrUtil.subBefore(authorization, ":", false);
111 clientSecret = StrUtil.subAfter(authorization, ":", false); 113 clientSecret = StrUtil.subAfter(authorization, ":", false);
112 - // 再从 Param 中获取 114 + // 再从 Param 中获取
113 } else { 115 } else {
114 clientId = request.getParameter("client_id"); 116 clientId = request.getParameter("client_id");
115 clientSecret = request.getParameter("client_secret"); 117 clientSecret = request.getParameter("client_secret");
@@ -122,5 +124,43 @@ public class HttpUtils { @@ -122,5 +124,43 @@ public class HttpUtils {
122 return null; 124 return null;
123 } 125 }
124 126
  127 + /**
  128 + * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
  129 + *
  130 + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
  131 + *
  132 + * @param url URL
  133 + * @param headers 请求头
  134 + * @param requestBody 请求体
  135 + * @return 请求结果
  136 + */
  137 + public static String post(String url, Map<String, String> headers, String requestBody) {
  138 +
  139 + try (HttpResponse response = HttpRequest.post(url)
  140 + .addHeaders(headers)
  141 + .body(requestBody)
  142 + .execute()) {
  143 + return response.body();
  144 + }
  145 + }
  146 +
  147 + /**
  148 + * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
  149 + *
  150 + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
  151 + *
  152 + * @param url URL
  153 + * @param headers 请求头
  154 + * @return 请求结果
  155 + */
  156 + public static String get(String url, Map<String, String> headers) {
  157 +
  158 + try (HttpResponse response = HttpRequest.get(url)
  159 + .addHeaders(headers)
  160 + .execute()) {
  161 + return response.body();
  162 + }
  163 + }
125 164
126 } 165 }
  166 +
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
@@ -2,10 +2,8 @@ package cn.iocoder.yudao.module.system.controller.admin.sms; @@ -2,10 +2,8 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5 -import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;  
6 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; 5 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
7 import cn.iocoder.yudao.module.system.service.sms.SmsSendService; 6 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
8 -import com.xingyuv.captcha.util.StreamUtils;  
9 import io.swagger.v3.oas.annotations.Operation; 7 import io.swagger.v3.oas.annotations.Operation;
10 import io.swagger.v3.oas.annotations.tags.Tag; 8 import io.swagger.v3.oas.annotations.tags.Tag;
11 import org.springframework.web.bind.annotation.*; 9 import org.springframework.web.bind.annotation.*;
@@ -14,8 +12,6 @@ import jakarta.annotation.Resource; @@ -14,8 +12,6 @@ import jakarta.annotation.Resource;
14 import jakarta.annotation.security.PermitAll; 12 import jakarta.annotation.security.PermitAll;
15 import jakarta.servlet.http.HttpServletRequest; 13 import jakarta.servlet.http.HttpServletRequest;
16 14
17 -import java.nio.charset.Charset;  
18 -  
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 15 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 16
21 @Tag(name = "管理后台 - 短信回调") 17 @Tag(name = "管理后台 - 短信回调")
@@ -29,7 +25,6 @@ public class SmsCallbackController { @@ -29,7 +25,6 @@ public class SmsCallbackController {
29 @PostMapping("/aliyun") 25 @PostMapping("/aliyun")
30 @PermitAll 26 @PermitAll
31 @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档") 27 @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
32 - @OperateLog(enable = false)  
33 public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { 28 public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
34 String text = ServletUtils.getBody(request); 29 String text = ServletUtils.getBody(request);
35 smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text); 30 smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
@@ -39,7 +34,6 @@ public class SmsCallbackController { @@ -39,7 +34,6 @@ public class SmsCallbackController {
39 @PostMapping("/tencent") 34 @PostMapping("/tencent")
40 @PermitAll 35 @PermitAll
41 @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档") 36 @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
42 - @OperateLog(enable = false)  
43 public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable { 37 public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
44 String text = ServletUtils.getBody(request); 38 String text = ServletUtils.getBody(request);
45 smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text); 39 smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
@@ -50,9 +44,18 @@ public class SmsCallbackController { @@ -50,9 +44,18 @@ public class SmsCallbackController {
50 @PostMapping("/huawei") 44 @PostMapping("/huawei")
51 @PermitAll 45 @PermitAll
52 @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") 46 @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档")
53 - @OperateLog(enable = false)  
54 public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable { 47 public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable {
55 smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody); 48 smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody);
56 return success(true); 49 return success(true);
57 } 50 }
  51 +
  52 + @PostMapping("/qiniu")
  53 + @PermitAll
  54 + @Operation(summary = "七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档")
  55 + public CommonResult<Boolean> receiveQiniuSmsStatus(@RequestBody String requestBody) throws Throwable {
  56 + smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody);
  57 + return success(true);
  58 + }
  59 +
58 } 60 }
  61 +
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
  2 +
  3 +import cn.hutool.core.collection.ListUtil;
  4 +import cn.hutool.core.date.LocalDateTimeUtil;
  5 +import cn.hutool.core.lang.Assert;
  6 +import cn.hutool.core.util.ObjectUtil;
  7 +import cn.hutool.crypto.digest.HMac;
  8 +import cn.hutool.crypto.digest.HmacAlgorithm;
  9 +import cn.hutool.json.JSONObject;
  10 +import cn.hutool.json.JSONUtil;
  11 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  12 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  13 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
  14 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
  15 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
  16 +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  17 +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
  18 +import com.google.common.annotations.VisibleForTesting;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +
  21 +import java.nio.charset.StandardCharsets;
  22 +import java.text.SimpleDateFormat;
  23 +
  24 +import java.util.*;
  25 +import java.util.stream.Collectors;
  26 +
  27 +/**
  28 + * 七牛云短信客户端的实现类
  29 + *
  30 + * @author scholar
  31 + * @since 2024/08/26 15:35
  32 + */
  33 +@Slf4j
  34 +public class QiniuSmsClient extends AbstractSmsClient {
  35 +
  36 + private static final String HOST = "sms.qiniuapi.com";
  37 +
  38 + private static final String PATH = "/v1/message/single";
  39 +
  40 + private static final String TEMPLATE_PATH = "/v1/template";
  41 +
  42 + public QiniuSmsClient(SmsChannelProperties properties) {
  43 + super(properties);
  44 + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
  45 + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
  46 + }
  47 +
  48 + @Override
  49 + protected void doInit() {
  50 + }
  51 + @Override
  52 + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
  53 + List<KeyValue<String, Object>> templateParams) throws Throwable {
  54 +
  55 + // 1. 执行请求
  56 + // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages
  57 + LinkedHashMap<String, Object> body = new LinkedHashMap<>();
  58 + Map<String, Object> paramsMap = templateParams.stream()
  59 + .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue));
  60 +
  61 + body.put("template_id", apiTemplateId);
  62 + body.put("mobile", mobile);
  63 + body.put("parameters", paramsMap);
  64 + body.put("seq", Long.toString(sendLogId));
  65 +
  66 + JSONObject response = request("POST", body, null);
  67 + // 2. 解析请求
  68 + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id"))
  69 + .setSerialNo(response.getStr("message_id"));
  70 + }
  71 +
  72 +
  73 + /**
  74 + * 请求七牛云短信
  75 + *
  76 + * @see <a href="https://developer.qiniu.com/sms/5842/sms-api-authentication"</>
  77 + * @param httpMethod http请求方法
  78 + * @param queryParams 请求参数
  79 + * @return 请求结果
  80 + */
  81 + private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, Map<String, Object> queryParams) {
  82 +
  83 + String signature = "";
  84 + String templateIdPath = "";
  85 +
  86 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
  87 + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
  88 + String signDate = dateFormat.format(new Date());
  89 +
  90 + //请求头
  91 + Map<String, String> header = new HashMap<>(4);
  92 + header.put("HOST", HOST);
  93 + header.put("Authorization", signature);
  94 + header.put("Content-Type", "application/json");
  95 + header.put("X-Qiniu-Date", signDate);
  96 +
  97 + String responseBody ="";
  98 + if(Objects.equals(httpMethod, "POST")){
  99 + header.put("Authorization", getSignature(httpMethod, HOST, PATH, JSONUtil.toJsonStr(body), signDate));
  100 + responseBody = HttpUtils.post("https://" + HOST + PATH, header, JSONUtil.toJsonStr(body));
  101 + }else { // GET
  102 + templateIdPath = TEMPLATE_PATH + "/" + queryParams.get("template_id");
  103 + header.put("Authorization", getSignature(httpMethod, HOST, templateIdPath, null, signDate));
  104 + responseBody = HttpUtils.get("https://" + HOST + templateIdPath, header);
  105 + }
  106 + return JSONUtil.parseObj(responseBody);
  107 + }
  108 +
  109 + public String getSignature(String method, String host, String path, String body, String signDate) {
  110 +
  111 + StringBuilder dataToSign = new StringBuilder();
  112 + dataToSign.append(method.toUpperCase()).append(" ").append(path);
  113 + dataToSign.append("\nHost: ").append(host);
  114 + dataToSign.append("\n").append("Content-Type").append(": ").append("application/json");
  115 + dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate);
  116 + dataToSign.append("\n\n");
  117 + if (ObjectUtil.isNotEmpty(body)) {
  118 + dataToSign.append(body);
  119 + }
  120 + HMac hMac = new HMac(HmacAlgorithm.HmacSHA1, properties.getApiSecret().getBytes(StandardCharsets.UTF_8));
  121 + byte[] signData = hMac.digest(dataToSign.toString().getBytes(StandardCharsets.UTF_8));
  122 + String encodedSignature = Base64.getEncoder().encodeToString(signData);
  123 +
  124 + return "Qiniu " + properties.getApiKey() + ":" + encodedSignature;
  125 + }
  126 +
  127 + @Override
  128 + public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
  129 +
  130 + JSONObject status = JSONUtil.parseObj(text);
  131 + //字段参考 https://developer.qiniu.com/sms/5910/message-push
  132 + return ListUtil.of(new SmsReceiveRespDTO()
  133 + .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功
  134 + .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status"))
  135 + .setMobile(status.getJSONArray("items").getJSONObject(0).getStr("mobile")) // 手机号
  136 + .setReceiveTime(LocalDateTimeUtil.of(status.getJSONArray("items").getJSONObject(0).getLong("delivrd_at")*1000L))
  137 + .setSerialNo(status.getJSONArray("items").getJSONObject(0).getStr("message_id")) // 发送序列号
  138 + .setLogId(Long.valueOf(status.getJSONArray("items").getJSONObject(0).getStr("seq")))); // logId
  139 + }
  140 +
  141 + @Override
  142 + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
  143 + // 1. 执行请求
  144 + // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template
  145 + HashMap<String, Object> queryParam = new HashMap<>();
  146 + queryParam.put("template_id", apiTemplateId);
  147 + JSONObject response = request("GET", null, queryParam);
  148 +
  149 + // 2.1 请求失败
  150 + String status = response.getStr("audit_status");
  151 + if (!Objects.equals(status, "passed")) {
  152 + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
  153 + return null;
  154 + }
  155 + // 2.2 请求成功
  156 + return new SmsTemplateRespDTO()
  157 + .setId(response.getStr("id"))
  158 + .setContent(response.getStr("template"))
  159 + .setAuditStatus(convertSmsTemplateAuditStatus(response.getStr("audit_status")))
  160 + .setAuditReason(response.getStr("reject_reason"));
  161 + }
  162 +
  163 + @VisibleForTesting
  164 + Integer convertSmsTemplateAuditStatus(String templateStatus) {
  165 +
  166 + if(Objects.equals(templateStatus, "passed")){
  167 + return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
  168 + }else {
  169 + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus));
  170 + }
  171 + }
  172 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java
@@ -18,6 +18,7 @@ public enum SmsChannelEnum { @@ -18,6 +18,7 @@ public enum SmsChannelEnum {
18 ALIYUN("ALIYUN", "阿里云"), 18 ALIYUN("ALIYUN", "阿里云"),
19 TENCENT("TENCENT", "腾讯云"), 19 TENCENT("TENCENT", "腾讯云"),
20 HUAWEI("HUAWEI", "华为云"), 20 HUAWEI("HUAWEI", "华为云"),
  21 + QINIU("QINIU", "七牛云"),
21 ; 22 ;
22 23
23 /** 24 /**
@@ -34,3 +35,4 @@ public enum SmsChannelEnum { @@ -34,3 +35,4 @@ public enum SmsChannelEnum {
34 } 35 }
35 36
36 } 37 }
  38 +
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  6 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
  7 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
  8 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
  9 +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  10 +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
  11 +import com.google.common.collect.Lists;
  12 +
  13 +import org.junit.jupiter.api.Test;
  14 +import org.mockito.InjectMocks;
  15 +import org.mockito.MockedStatic;
  16 +
  17 +import java.time.LocalDateTime;
  18 +import java.util.List;
  19 +
  20 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  21 +import static org.junit.jupiter.api.Assertions.*;
  22 +import static org.mockito.ArgumentMatchers.*;
  23 +import static org.mockito.ArgumentMatchers.anyString;
  24 +import static org.mockito.Mockito.mockStatic;
  25 +
  26 +/**
  27 + * {@link QiniuSmsClient} 的单元测试
  28 + *
  29 + * @author scholar
  30 + */
  31 +public class QiniuSmsClientTest extends BaseMockitoUnitTest {
  32 +
  33 + private final SmsChannelProperties properties = new SmsChannelProperties()
  34 + .setApiKey(randomString())// 随机一个 apiKey,避免构建报错
  35 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
  36 + .setSignature("芋道源码");
  37 +
  38 + @InjectMocks
  39 + private QiniuSmsClient smsClient = new QiniuSmsClient(properties);
  40 +
  41 + @Test
  42 + public void testDoInit() {
  43 + // 调用
  44 + smsClient.doInit();
  45 + }
  46 +
  47 + @Test
  48 + public void testDoSendSms_success() throws Throwable {
  49 +
  50 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  51 + // 准备参数
  52 + Long sendLogId = randomLongId();
  53 + String mobile = randomString();
  54 + String apiTemplateId = randomString() + " " + randomString();
  55 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  56 + new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
  57 + // mock 方法
  58 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
  59 + .thenReturn(
  60 + "{\"message_id\":\"17245678901\"}"
  61 + );
  62 + // 调用
  63 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
  64 + apiTemplateId, templateParams);
  65 + // 断言
  66 + assertTrue(result.getSuccess());
  67 + assertEquals("17245678901", result.getSerialNo());
  68 + }
  69 + }
  70 +
  71 + @Test
  72 + public void testDoSendSms_fail() throws Throwable {
  73 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  74 + // 准备参数
  75 + Long sendLogId = randomLongId();
  76 + String mobile = randomString();
  77 + String apiTemplateId = randomString() + " " + randomString();
  78 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  79 + new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
  80 +
  81 + // mock 方法
  82 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
  83 + .thenReturn(
  84 + "{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"
  85 + );
  86 + // 调用
  87 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
  88 + apiTemplateId, templateParams);
  89 + // 断言
  90 + assertFalse(result.getSuccess());
  91 + }
  92 + }
  93 +
  94 + @Test
  95 + public void testGetSmsTemplate() throws Throwable {
  96 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  97 + // 准备参数
  98 + String apiTemplateId = randomString();
  99 + // mock 方法
  100 + httpUtilsMockedStatic.when(() -> HttpUtils.get(anyString(), anyMap()))
  101 + .thenReturn("{\"audit_status\":\"passed\",\"created_at\":1724231187,\"description\":\"\",\"disable_broadcast\":false,\"disable_broadcast_reason\":\"\",\"disable_reason\":\"\",\"disabled\":false,\"id\":\"1826184073773596672\",\"is_oversea\":false,\"name\":\"dd\",\"parameters\":[\"code\"],\"reject_reason\":\"\",\"signature_id\":\"1826099896017498112\",\"signature_text\":\"yudao\",\"template\":\"您的验证码为:${code}\",\"type\":\"verification\",\"uid\":1383022432,\"updated_at\":1724288561,\"variable_count\":0}");
  102 + // 调用
  103 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
  104 + // 断言
  105 + assertEquals("1826184073773596672", result.getId());
  106 + assertEquals("您的验证码为:${code}", result.getContent());
  107 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
  108 + assertEquals("", result.getAuditReason());
  109 + }
  110 + }
  111 +
  112 + @Test
  113 + public void testParseSmsReceiveStatus() {
  114 + // 准备参数
  115 + String text = "{\"items\":[{\"mobile\":\"18881234567\",\"message_id\":\"10135515063508004167\",\"status\":\"DELIVRD\",\"delivrd_at\":1724591666,\"error\":\"DELIVRD\",\"seq\":\"123\"}]}";
  116 + // 调用
  117 + List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
  118 + // 断言
  119 + assertEquals(1, statuses.size());
  120 + assertTrue(statuses.getFirst().getSuccess());
  121 + assertEquals("DELIVRD", statuses.getFirst().getErrorMsg());
  122 + assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), statuses.getFirst().getReceiveTime());
  123 + assertEquals("18881234567", statuses.getFirst().getMobile());
  124 + assertEquals("10135515063508004167", statuses.getFirst().getSerialNo());
  125 + assertEquals(123, statuses.getFirst().getLogId());
  126 + }
  127 +
  128 +}
0 \ No newline at end of file 129 \ No newline at end of file
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
1 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; 1 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
2 2
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; 4 +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
5 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; 5 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
6 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; 6 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
7 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; 7 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
@@ -11,39 +11,19 @@ import org.junit.jupiter.api.Test; @@ -11,39 +11,19 @@ import org.junit.jupiter.api.Test;
11 import java.util.List; 11 import java.util.List;
12 12
13 /** 13 /**
14 - * 各种 {@link SmsClientTests 集成测试 14 + * 各种 {@link SmsClient} 的集成测试
15 * 15 *
16 * @author 芋道源码 16 * @author 芋道源码
17 */ 17 */
18 public class SmsClientTests { 18 public class SmsClientTests {
19 19
20 - @Test  
21 - @Disabled  
22 - public void testHuaweiSmsClient_sendSms() throws Throwable {  
23 - SmsChannelProperties properties = new SmsChannelProperties()  
24 - .setApiKey("123")  
25 - .setApiSecret("456")  
26 - .setSignature("runpu");  
27 - HuaweiSmsClient client = new HuaweiSmsClient(properties);  
28 - // 准备参数  
29 - Long sendLogId = System.currentTimeMillis();  
30 - String mobile = "15601691323";  
31 - String apiTemplateId = "xx test01";  
32 - List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024"));  
33 - // 调用  
34 - SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);  
35 - // 打印结果  
36 - System.out.println(smsSendRespDTO);  
37 - }  
38 -  
39 // ========== 阿里云 ========== 20 // ========== 阿里云 ==========
40 -  
41 @Test 21 @Test
42 @Disabled 22 @Disabled
43 public void testAliyunSmsClient_getSmsTemplate() throws Throwable { 23 public void testAliyunSmsClient_getSmsTemplate() throws Throwable {
44 SmsChannelProperties properties = new SmsChannelProperties() 24 SmsChannelProperties properties = new SmsChannelProperties()
45 - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")  
46 - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); 25 + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY"))
  26 + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY"));
47 AliyunSmsClient client = new AliyunSmsClient(properties); 27 AliyunSmsClient client = new AliyunSmsClient(properties);
48 // 准备参数 28 // 准备参数
49 String apiTemplateId = "SMS_207945135"; 29 String apiTemplateId = "SMS_207945135";
@@ -57,9 +37,9 @@ public class SmsClientTests { @@ -57,9 +37,9 @@ public class SmsClientTests {
57 @Disabled 37 @Disabled
58 public void testAliyunSmsClient_sendSms() throws Throwable { 38 public void testAliyunSmsClient_sendSms() throws Throwable {
59 SmsChannelProperties properties = new SmsChannelProperties() 39 SmsChannelProperties properties = new SmsChannelProperties()
60 - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")  
61 - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")  
62 - .setSignature("runpu"); 40 + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY"))
  41 + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY"))
  42 + .setSignature("Ballcat");
63 AliyunSmsClient client = new AliyunSmsClient(properties); 43 AliyunSmsClient client = new AliyunSmsClient(properties);
64 // 准备参数 44 // 准备参数
65 Long sendLogId = System.currentTimeMillis(); 45 Long sendLogId = System.currentTimeMillis();
@@ -71,49 +51,21 @@ public class SmsClientTests { @@ -71,49 +51,21 @@ public class SmsClientTests {
71 System.out.println(sendRespDTO); 51 System.out.println(sendRespDTO);
72 } 52 }
73 53
74 - @Test  
75 - @Disabled  
76 - public void testAliyunSmsClient_parseSmsReceiveStatus() {  
77 - SmsChannelProperties properties = new SmsChannelProperties()  
78 - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")  
79 - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz");  
80 - AliyunSmsClient client = new AliyunSmsClient(properties);  
81 - // 准备参数  
82 - String text = "[\n" +  
83 - " {\n" +  
84 - " \"phone_number\" : \"13900000001\",\n" +  
85 - " \"send_time\" : \"2017-01-01 11:12:13\",\n" +  
86 - " \"report_time\" : \"2017-02-02 22:23:24\",\n" +  
87 - " \"success\" : true,\n" +  
88 - " \"err_code\" : \"DELIVERED\",\n" +  
89 - " \"err_msg\" : \"用户接收成功\",\n" +  
90 - " \"sms_size\" : \"1\",\n" +  
91 - " \"biz_id\" : \"12345\",\n" +  
92 - " \"out_id\" : \"67890\"\n" +  
93 - " }\n" +  
94 - "]";  
95 - // mock 方法  
96 -  
97 - // 调用  
98 - List<SmsReceiveRespDTO> statuses = client.parseSmsReceiveStatus(text);  
99 - // 打印结果  
100 - System.out.println(statuses);  
101 - }  
102 -  
103 // ========== 腾讯云 ========== 54 // ========== 腾讯云 ==========
104 55
105 @Test 56 @Test
106 @Disabled 57 @Disabled
107 public void testTencentSmsClient_sendSms() throws Throwable { 58 public void testTencentSmsClient_sendSms() throws Throwable {
  59 + String sdkAppId = "1400500458";
108 SmsChannelProperties properties = new SmsChannelProperties() 60 SmsChannelProperties properties = new SmsChannelProperties()
109 - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")  
110 - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") 61 + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId)
  62 + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY"))
111 .setSignature("芋道源码"); 63 .setSignature("芋道源码");
112 TencentSmsClient client = new TencentSmsClient(properties); 64 TencentSmsClient client = new TencentSmsClient(properties);
113 // 准备参数 65 // 准备参数
114 Long sendLogId = System.currentTimeMillis(); 66 Long sendLogId = System.currentTimeMillis();
115 String mobile = "15601691323"; 67 String mobile = "15601691323";
116 - String apiTemplateId = "2136358"; 68 + String apiTemplateId = "358212";
117 // 调用 69 // 调用
118 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); 70 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));
119 // 打印结果 71 // 打印结果
@@ -123,17 +75,77 @@ public class SmsClientTests { @@ -123,17 +75,77 @@ public class SmsClientTests {
123 @Test 75 @Test
124 @Disabled 76 @Disabled
125 public void testTencentSmsClient_getSmsTemplate() throws Throwable { 77 public void testTencentSmsClient_getSmsTemplate() throws Throwable {
  78 + String sdkAppId = "1400500458";
126 SmsChannelProperties properties = new SmsChannelProperties() 79 SmsChannelProperties properties = new SmsChannelProperties()
127 - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")  
128 - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") 80 + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId)
  81 + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY"))
129 .setSignature("芋道源码"); 82 .setSignature("芋道源码");
130 TencentSmsClient client = new TencentSmsClient(properties); 83 TencentSmsClient client = new TencentSmsClient(properties);
131 // 准备参数 84 // 准备参数
132 - String apiTemplateId = "2136358"; 85 + String apiTemplateId = "358212";
  86 + // 调用
  87 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId);
  88 + // 打印结果
  89 + System.out.println(template);
  90 + }
  91 +
  92 + // ========== 华为云 ==========
  93 +
  94 + @Test
  95 + @Disabled
  96 + public void testHuaweiSmsClient_sendSms() throws Throwable {
  97 + String sender = "x8824060312575";
  98 + SmsChannelProperties properties = new SmsChannelProperties()
  99 + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY") + " " + sender)
  100 + .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY"))
  101 + .setSignature("runpu");
  102 + HuaweiSmsClient client = new HuaweiSmsClient(properties);
  103 + // 准备参数
  104 + Long sendLogId = System.currentTimeMillis();
  105 + String mobile = "17321315478";
  106 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
  107 + List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024"));
  108 + // 调用
  109 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
  110 + // 打印结果
  111 + System.out.println(smsSendRespDTO);
  112 + }
  113 +
  114 + // ========== 七牛云 ==========
  115 +
  116 + @Test
  117 + @Disabled
  118 + public void testQiniuSmsClient_sendSms() throws Throwable {
  119 +
  120 + SmsChannelProperties properties = new SmsChannelProperties()
  121 + .setApiKey("SMS_QINIU_ACCESS_KEY")
  122 + .setApiSecret("SMS_QINIU_SECRET_KEY");
  123 + QiniuSmsClient client = new QiniuSmsClient(properties);
  124 + // 准备参数
  125 + Long sendLogId = System.currentTimeMillis();
  126 + String mobile = "17321315478";
  127 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
  128 + List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1122"));
  129 + // 调用
  130 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
  131 + // 打印结果
  132 + System.out.println(smsSendRespDTO);
  133 + }
  134 +
  135 + @Test
  136 + @Disabled
  137 + public void testQiniuSmsClient_getSmsTemplate() throws Throwable {
  138 +
  139 + SmsChannelProperties properties = new SmsChannelProperties()
  140 + .setApiKey("SMS_QINIU_ACCESS_KEY")
  141 + .setApiSecret("SMS_QINIU_SECRET_KEY");
  142 + QiniuSmsClient client = new QiniuSmsClient(properties);
  143 + // 准备参数
  144 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
133 // 调用 145 // 调用
134 SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); 146 SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId);
135 // 打印结果 147 // 打印结果
136 System.out.println(template); 148 System.out.println(template);
137 } 149 }
138 -}  
139 150
  151 +}
140 \ No newline at end of file 152 \ No newline at end of file