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 1697d097f..456b4007e 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 @@ -135,7 +135,6 @@ public class HttpUtils { * @return 请求结果 */ public static String post(String url, Map headers, String requestBody) { - try (HttpResponse response = HttpRequest.post(url) .addHeaders(headers) .body(requestBody) @@ -154,7 +153,6 @@ public class HttpUtils { * @return 请求结果 */ public static String get(String url, Map headers) { - try (HttpResponse response = HttpRequest.get(url) .addHeaders(headers) .execute()) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java index c0a2b60ac..4fbb8649d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -1,10 +1,13 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; @@ -18,11 +21,7 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; - import java.util.*; -import java.util.stream.Collectors; /** * 七牛云短信客户端的实现类 @@ -45,69 +44,60 @@ public class QiniuSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override protected void doInit() { } - @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 1. 执行请求 // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages LinkedHashMap body = new LinkedHashMap<>(); - Map paramsMap = templateParams.stream() - .collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue)); - body.put("template_id", apiTemplateId); body.put("mobile", mobile); - body.put("parameters", paramsMap); + body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); body.put("seq", Long.toString(sendLogId)); - JSONObject response = request("POST", body, null); + JSONObject response = request("POST", body, PATH); // 2. 解析请求 + if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("error")) + .setApiRequestId(response.getStr("request_id")) + .setApiMsg(response.getStr("message")); + } + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) .setSerialNo(response.getStr("message_id")); } - /** * 请求七牛云短信 * * @see * @param httpMethod http请求方法 - * @param queryParams 请求参数 + * @param body http请求消息体 + * @param path URL path * @return 请求结果 */ - private JSONObject request(String httpMethod, LinkedHashMap body, Map queryParams) { - - String signature = ""; - String templateIdPath = ""; - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - String signDate = dateFormat.format(new Date()); - + private JSONObject request(String httpMethod, LinkedHashMap body, String path) { + String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'"); //请求头 Map header = new HashMap<>(4); header.put("HOST", HOST); - header.put("Authorization", signature); + header.put("Authorization", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); header.put("Content-Type", "application/json"); header.put("X-Qiniu-Date", signDate); String responseBody =""; - if(Objects.equals(httpMethod, "POST")){ - header.put("Authorization", getSignature(httpMethod, HOST, PATH, JSONUtil.toJsonStr(body), signDate)); - responseBody = HttpUtils.post("https://" + HOST + PATH, header, JSONUtil.toJsonStr(body)); - }else { // GET - templateIdPath = TEMPLATE_PATH + "/" + queryParams.get("template_id"); - header.put("Authorization", getSignature(httpMethod, HOST, templateIdPath, null, signDate)); - responseBody = HttpUtils.get("https://" + HOST + templateIdPath, header); + if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求 + responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); + }else { // GET 查询template状态用GET请求 + responseBody = HttpUtils.get("https://" + HOST + path, header); } return JSONUtil.parseObj(responseBody); } public String getSignature(String method, String host, String path, String body, String signDate) { - StringBuilder dataToSign = new StringBuilder(); dataToSign.append(method.toUpperCase()).append(" ").append(path); dataToSign.append("\nHost: ").append(host); @@ -117,18 +107,15 @@ public class QiniuSmsClient extends AbstractSmsClient { if (ObjectUtil.isNotEmpty(body)) { dataToSign.append(body); } - HMac hMac = new HMac(HmacAlgorithm.HmacSHA1, properties.getApiSecret().getBytes(StandardCharsets.UTF_8)); - byte[] signData = hMac.digest(dataToSign.toString().getBytes(StandardCharsets.UTF_8)); - String encodedSignature = Base64.getEncoder().encodeToString(signData); + String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true); return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; } @Override public List parseSmsReceiveStatus(String text) { - JSONObject status = JSONUtil.parseObj(text); - //字段参考 https://developer.qiniu.com/sms/5910/message-push + // 字段参考 https://developer.qiniu.com/sms/5910/message-push return ListUtil.of(new SmsReceiveRespDTO() .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) @@ -142,16 +129,13 @@ public class QiniuSmsClient extends AbstractSmsClient { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { // 1. 执行请求 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template - HashMap queryParam = new HashMap<>(); - queryParam.put("template_id", apiTemplateId); - JSONObject response = request("GET", null, queryParam); - + JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId); // 2.1 请求失败 - String status = response.getStr("audit_status"); - if (!Objects.equals(status, "passed")) { + if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) { log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); return null; } + // 2.2 请求成功 return new SmsTemplateRespDTO() .setId(response.getStr("id")) @@ -162,11 +146,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @VisibleForTesting Integer convertSmsTemplateAuditStatus(String templateStatus) { - - if(Objects.equals(templateStatus, "passed")){ - return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); - }else { - throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); - } + return switch (templateStatus) { + case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus(); + case null, default -> + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + }; } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java index c64c39470..c3e896695 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.mockStatic; * @author scholar */ public class QiniuSmsClientTest extends BaseMockitoUnitTest { - private final SmsChannelProperties properties = new SmsChannelProperties() .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 @@ -46,7 +45,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @Test public void testDoSendSms_success() throws Throwable { - try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { // 准备参数 Long sendLogId = randomLongId(); @@ -56,9 +54,7 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"message_id\":\"17245678901\"}" - ); + .thenReturn("{\"message_id\":\"17245678901\"}"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); @@ -77,17 +73,17 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { String apiTemplateId = randomString() + " " + randomString(); List> templateParams = Lists.newArrayList( new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); - // mock 方法 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) - .thenReturn( - "{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}" - ); + .thenReturn("{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"); // 调用 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); // 断言 assertFalse(result.getSuccess()); + assertEquals("BadToken", result.getApiCode()); + assertEquals("Your authorization token is invalid", result.getApiMsg()); + assertEquals("etziWcJFo1C8Ne8X", result.getApiRequestId()); } } @@ -125,4 +121,15 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { assertEquals(123, statuses.getFirst().getLogId()); } + @Test + public void testConvertSmsTemplateAuditStatus() { + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), + smsClient.convertSmsTemplateAuditStatus("passed")); + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(), + smsClient.convertSmsTemplateAuditStatus("reviewing")); + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(), + smsClient.convertSmsTemplateAuditStatus("rejected")); + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("unknown"), + "未知审核状态(3)"); + } } \ No newline at end of file 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 3752e5763..4f003ebaf 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 @@ -116,7 +116,6 @@ public class SmsClientTests { @Test @Disabled public void testQiniuSmsClient_sendSms() throws Throwable { - SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("SMS_QINIU_ACCESS_KEY") .setApiSecret("SMS_QINIU_SECRET_KEY"); @@ -135,7 +134,6 @@ public class SmsClientTests { @Test @Disabled public void testQiniuSmsClient_getSmsTemplate() throws Throwable { - SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("SMS_QINIU_ACCESS_KEY") .setApiSecret("SMS_QINIU_SECRET_KEY");