diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 9a39a7a4e..9da4f87b1 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/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; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -122,5 +124,23 @@ public class HttpUtils { return null; } + /** + * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @param requestBody 请求体 + * @return 请求结果 + */ + public static String post(String url, Map headers, String requestBody) { + try (HttpResponse response = HttpRequest.post(url) + .addHeaders(headers) + .body(requestBody) + .execute()) { + return response.body(); + } + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 7f5096e4e..ed6dd7a8d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -6,13 +6,12 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; @@ -58,10 +57,11 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { + Assert.notBlank(properties.getSignature(), "短信签名不能为空"); // 1. 执行请求 // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms TreeMap queryParam = new TreeMap<>(); - queryParam.put("PhoneNumbers",mobile); + queryParam.put("PhoneNumbers", mobile); queryParam.put("SignName", properties.getSignature()); queryParam.put("TemplateCode", apiTemplateId); queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); @@ -111,7 +111,8 @@ public class AliyunSmsClient extends AbstractSmsClient { return null; } // 2.2 请求成功 - return new SmsTemplateRespDTO().setId(apiTemplateId) + return new SmsTemplateRespDTO() + .setId(response.getStr("TemplateCode")) .setContent(response.getStr("TemplateContent")) .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) .setAuditReason(response.getStr("Reason")); @@ -176,10 +177,8 @@ public class AliyunSmsClient extends AbstractSmsClient { + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); // 5. 发起请求 - try (HttpResponse response = HttpRequest.post(URL + "?" + queryString) - .addHeaders(headers).body(requestBody).execute()) { - return JSONUtil.parseObj(response.body()); - } + String responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody); + return JSONUtil.parseObj(responseBody); } /** diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java index bc5a47140..c6e015d81 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java @@ -1,21 +1,30 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; +import org.mockito.MockedStatic; import java.time.LocalDateTime; import java.util.List; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; -// TODO 芋艿:需要优化 /** - * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient_old} 的单元测试 + * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试 * * @author 芋道源码 */ @@ -38,64 +47,54 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { smsClient.doInit(); } -// @Test -// public void tesSendSms_success() throws Throwable { -// // 准备参数 -// Long sendLogId = randomLongId(); -// String mobile = randomString(); -// String apiTemplateId = randomString(); -// List> templateParams = Lists.newArrayList( -// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); -// // mock 方法 -// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK")); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(mobile, acsRequest.getPhoneNumbers()); -// assertEquals(properties.getSignature(), acsRequest.getSignName()); -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); -// assertEquals(sendLogId.toString(), acsRequest.getOutId()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, -// apiTemplateId, templateParams); -// // 断言 -// assertTrue(result.getSuccess()); -// assertEquals(response.getRequestId(), result.getApiRequestId()); -// assertEquals(response.getCode(), result.getApiCode()); -// assertEquals(response.getMessage(), result.getApiMsg()); -// assertEquals(response.getBizId(), result.getSerialNo()); -// } + @Test + public void tesSendSms_success() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"OK\",\"RequestId\":\"30067CE9-3710-5984-8881-909B21D8DB28\",\"Code\":\"OK\",\"BizId\":\"800025323183427988\"}"); -// @Test -// public void tesSendSms_fail() throws Throwable { -// // 准备参数 -// Long sendLogId = randomLongId(); -// String mobile = randomString(); -// String apiTemplateId = randomString(); -// List> templateParams = Lists.newArrayList( -// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); -// // mock 方法 -// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR")); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(mobile, acsRequest.getPhoneNumbers()); -// assertEquals(properties.getSignature(), acsRequest.getSignName()); -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); -// assertEquals(sendLogId.toString(), acsRequest.getOutId()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); -// // 断言 -// assertFalse(result.getSuccess()); -// assertEquals(response.getRequestId(), result.getApiRequestId()); -// assertEquals(response.getCode(), result.getApiCode()); -// assertEquals(response.getMessage(), result.getApiMsg()); -// assertEquals(response.getBizId(), result.getSerialNo()); -// } + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("30067CE9-3710-5984-8881-909B21D8DB28", result.getApiRequestId()); + assertEquals("OK", result.getApiCode()); + assertEquals("OK", result.getApiMsg()); + assertEquals("800025323183427988", result.getSerialNo()); + } + } + + @Test + public void tesSendSms_fail() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"手机号码格式错误\",\"RequestId\":\"B7700B8E-227E-5886-9564-26036172F01F\",\"Code\":\"isv.MOBILE_NUMBER_ILLEGAL\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("B7700B8E-227E-5886-9564-26036172F01F", result.getApiRequestId()); + assertEquals("isv.MOBILE_NUMBER_ILLEGAL", result.getApiCode()); + assertEquals("手机号码格式错误", result.getApiMsg()); + assertNull(result.getSerialNo()); + } + } @Test public void testParseSmsReceiveStatus() { @@ -129,28 +128,24 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { assertEquals(67890L, statuses.get(0).getLogId()); } -// @Test -// public void testGetSmsTemplate() throws Throwable { -// // 准备参数 -// String apiTemplateId = randomString(); -// // mock 方法 -// QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> { -// o.setCode("OK"); -// o.setTemplateStatus(1); // 设置模板通过 -// }); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); -// // 断言 -// assertEquals(response.getTemplateCode(), result.getId()); -// assertEquals(response.getTemplateContent(), result.getContent()); -// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); -// assertEquals(response.getReason(), result.getAuditReason()); -// } + @Test + public void testGetSmsTemplate() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = randomString(); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"TemplateCode\":\"SMS_207945135\",\"RequestId\":\"6F4CC077-29C8-5BA5-AB62-5FF95068A5AC\",\"Message\":\"OK\",\"TemplateContent\":\"您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!\",\"TemplateName\":\"公告通知\",\"TemplateType\":0,\"Code\":\"OK\",\"CreateDate\":\"2020-12-23 17:34:42\",\"Reason\":\"无审批备注\",\"TemplateStatus\":1}"); + + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("SMS_207945135", result.getId()); + assertEquals("您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("无审批备注", result.getAuditReason()); + } + } @Test public void testConvertSmsTemplateAuditStatus() { diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 13848d33c..a5f31b4a2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -57,11 +57,12 @@ public class SmsClientTests { public void testAliyunSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setSignature("Ballcat"); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); - String mobile = "17321315478"; + String mobile = "173213154791"; String apiTemplateId = "SMS_207945135"; // 调用 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));