Commit 1c1abae5bbf8357fb32044e766cb92b06603dcfe

Authored by scholar
1 parent 4f7ac969

七牛云短信实现,评审意见修改

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
@@ -135,7 +135,6 @@ public class HttpUtils { @@ -135,7 +135,6 @@ public class HttpUtils {
135 * @return 请求结果 135 * @return 请求结果
136 */ 136 */
137 public static String post(String url, Map<String, String> headers, String requestBody) { 137 public static String post(String url, Map<String, String> headers, String requestBody) {
138 -  
139 try (HttpResponse response = HttpRequest.post(url) 138 try (HttpResponse response = HttpRequest.post(url)
140 .addHeaders(headers) 139 .addHeaders(headers)
141 .body(requestBody) 140 .body(requestBody)
@@ -154,7 +153,6 @@ public class HttpUtils { @@ -154,7 +153,6 @@ public class HttpUtils {
154 * @return 请求结果 153 * @return 请求结果
155 */ 154 */
156 public static String get(String url, Map<String, String> headers) { 155 public static String get(String url, Map<String, String> headers) {
157 -  
158 try (HttpResponse response = HttpRequest.get(url) 156 try (HttpResponse response = HttpRequest.get(url)
159 .addHeaders(headers) 157 .addHeaders(headers)
160 .execute()) { 158 .execute()) {
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.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.hutool.core.collection.CollStreamUtil;
3 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.date.LocalDateTimeUtil; 6 import cn.hutool.core.date.LocalDateTimeUtil;
5 import cn.hutool.core.lang.Assert; 7 import cn.hutool.core.lang.Assert;
  8 +import cn.hutool.core.util.ObjUtil;
6 import cn.hutool.core.util.ObjectUtil; 9 import cn.hutool.core.util.ObjectUtil;
7 -import cn.hutool.crypto.digest.HMac; 10 +import cn.hutool.crypto.SecureUtil;
8 import cn.hutool.crypto.digest.HmacAlgorithm; 11 import cn.hutool.crypto.digest.HmacAlgorithm;
9 import cn.hutool.json.JSONObject; 12 import cn.hutool.json.JSONObject;
10 import cn.hutool.json.JSONUtil; 13 import cn.hutool.json.JSONUtil;
@@ -18,11 +21,7 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp @@ -18,11 +21,7 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp
18 import com.google.common.annotations.VisibleForTesting; 21 import com.google.common.annotations.VisibleForTesting;
19 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
20 23
21 -import java.nio.charset.StandardCharsets;  
22 -import java.text.SimpleDateFormat;  
23 -  
24 import java.util.*; 24 import java.util.*;
25 -import java.util.stream.Collectors;  
26 25
27 /** 26 /**
28 * 七牛云短信客户端的实现类 27 * 七牛云短信客户端的实现类
@@ -45,69 +44,60 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -45,69 +44,60 @@ public class QiniuSmsClient extends AbstractSmsClient {
45 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); 44 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
46 } 45 }
47 46
48 - @Override  
49 protected void doInit() { 47 protected void doInit() {
50 } 48 }
51 - @Override 49 +
52 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, 50 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
53 List<KeyValue<String, Object>> templateParams) throws Throwable { 51 List<KeyValue<String, Object>> templateParams) throws Throwable {
54 -  
55 // 1. 执行请求 52 // 1. 执行请求
56 // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages 53 // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages
57 LinkedHashMap<String, Object> body = new LinkedHashMap<>(); 54 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); 55 body.put("template_id", apiTemplateId);
62 body.put("mobile", mobile); 56 body.put("mobile", mobile);
63 - body.put("parameters", paramsMap); 57 + body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue));
64 body.put("seq", Long.toString(sendLogId)); 58 body.put("seq", Long.toString(sendLogId));
65 59
66 - JSONObject response = request("POST", body, null); 60 + JSONObject response = request("POST", body, PATH);
67 // 2. 解析请求 61 // 2. 解析请求
  62 + if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败
  63 + return new SmsSendRespDTO().setSuccess(false)
  64 + .setApiCode(response.getStr("error"))
  65 + .setApiRequestId(response.getStr("request_id"))
  66 + .setApiMsg(response.getStr("message"));
  67 + }
  68 +
68 return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) 69 return new SmsSendRespDTO().setSuccess(response.containsKey("message_id"))
69 .setSerialNo(response.getStr("message_id")); 70 .setSerialNo(response.getStr("message_id"));
70 } 71 }
71 72
72 -  
73 /** 73 /**
74 * 请求七牛云短信 74 * 请求七牛云短信
75 * 75 *
76 * @see <a href="https://developer.qiniu.com/sms/5842/sms-api-authentication"</> 76 * @see <a href="https://developer.qiniu.com/sms/5842/sms-api-authentication"</>
77 * @param httpMethod http请求方法 77 * @param httpMethod http请求方法
78 - * @param queryParams 请求参数 78 + * @param body http请求消息体
  79 + * @param path URL path
79 * @return 请求结果 80 * @return 请求结果
80 */ 81 */
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 - 82 + private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, String path) {
  83 + String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'");
90 //请求头 84 //请求头
91 Map<String, String> header = new HashMap<>(4); 85 Map<String, String> header = new HashMap<>(4);
92 header.put("HOST", HOST); 86 header.put("HOST", HOST);
93 - header.put("Authorization", signature); 87 + header.put("Authorization", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate));
94 header.put("Content-Type", "application/json"); 88 header.put("Content-Type", "application/json");
95 header.put("X-Qiniu-Date", signDate); 89 header.put("X-Qiniu-Date", signDate);
96 90
97 String responseBody =""; 91 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); 92 + if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求
  93 + responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body));
  94 + }else { // GET 查询template状态用GET请求
  95 + responseBody = HttpUtils.get("https://" + HOST + path, header);
105 } 96 }
106 return JSONUtil.parseObj(responseBody); 97 return JSONUtil.parseObj(responseBody);
107 } 98 }
108 99
109 public String getSignature(String method, String host, String path, String body, String signDate) { 100 public String getSignature(String method, String host, String path, String body, String signDate) {
110 -  
111 StringBuilder dataToSign = new StringBuilder(); 101 StringBuilder dataToSign = new StringBuilder();
112 dataToSign.append(method.toUpperCase()).append(" ").append(path); 102 dataToSign.append(method.toUpperCase()).append(" ").append(path);
113 dataToSign.append("\nHost: ").append(host); 103 dataToSign.append("\nHost: ").append(host);
@@ -117,18 +107,15 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -117,18 +107,15 @@ public class QiniuSmsClient extends AbstractSmsClient {
117 if (ObjectUtil.isNotEmpty(body)) { 107 if (ObjectUtil.isNotEmpty(body)) {
118 dataToSign.append(body); 108 dataToSign.append(body);
119 } 109 }
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); 110 + String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true);
123 111
124 return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; 112 return "Qiniu " + properties.getApiKey() + ":" + encodedSignature;
125 } 113 }
126 114
127 @Override 115 @Override
128 public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) { 116 public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
129 -  
130 JSONObject status = JSONUtil.parseObj(text); 117 JSONObject status = JSONUtil.parseObj(text);
131 - //字段参考 https://developer.qiniu.com/sms/5910/message-push 118 + // 字段参考 https://developer.qiniu.com/sms/5910/message-push
132 return ListUtil.of(new SmsReceiveRespDTO() 119 return ListUtil.of(new SmsReceiveRespDTO()
133 .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 120 .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功
134 .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) 121 .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status"))
@@ -142,16 +129,13 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -142,16 +129,13 @@ public class QiniuSmsClient extends AbstractSmsClient {
142 public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { 129 public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
143 // 1. 执行请求 130 // 1. 执行请求
144 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template 131 // 参考链接 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 - 132 + JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId);
149 // 2.1 请求失败 133 // 2.1 请求失败
150 - String status = response.getStr("audit_status");  
151 - if (!Objects.equals(status, "passed")) { 134 + if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) {
152 log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); 135 log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
153 return null; 136 return null;
154 } 137 }
  138 +
155 // 2.2 请求成功 139 // 2.2 请求成功
156 return new SmsTemplateRespDTO() 140 return new SmsTemplateRespDTO()
157 .setId(response.getStr("id")) 141 .setId(response.getStr("id"))
@@ -162,11 +146,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -162,11 +146,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
162 146
163 @VisibleForTesting 147 @VisibleForTesting
164 Integer convertSmsTemplateAuditStatus(String templateStatus) { 148 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 - } 149 + return switch (templateStatus) {
  150 + case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
  151 + case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus();
  152 + case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus();
  153 + case null, default ->
  154 + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus));
  155 + };
171 } 156 }
172 } 157 }
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; @@ -29,7 +29,6 @@ import static org.mockito.Mockito.mockStatic;
29 * @author scholar 29 * @author scholar
30 */ 30 */
31 public class QiniuSmsClientTest extends BaseMockitoUnitTest { 31 public class QiniuSmsClientTest extends BaseMockitoUnitTest {
32 -  
33 private final SmsChannelProperties properties = new SmsChannelProperties() 32 private final SmsChannelProperties properties = new SmsChannelProperties()
34 .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 33 .setApiKey(randomString())// 随机一个 apiKey,避免构建报错
35 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 34 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
@@ -46,7 +45,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -46,7 +45,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
46 45
47 @Test 46 @Test
48 public void testDoSendSms_success() throws Throwable { 47 public void testDoSendSms_success() throws Throwable {
49 -  
50 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { 48 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
51 // 准备参数 49 // 准备参数
52 Long sendLogId = randomLongId(); 50 Long sendLogId = randomLongId();
@@ -56,9 +54,7 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -56,9 +54,7 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
56 new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); 54 new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
57 // mock 方法 55 // mock 方法
58 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) 56 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
59 - .thenReturn(  
60 - "{\"message_id\":\"17245678901\"}"  
61 - ); 57 + .thenReturn("{\"message_id\":\"17245678901\"}");
62 // 调用 58 // 调用
63 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, 59 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
64 apiTemplateId, templateParams); 60 apiTemplateId, templateParams);
@@ -77,17 +73,17 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -77,17 +73,17 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
77 String apiTemplateId = randomString() + " " + randomString(); 73 String apiTemplateId = randomString() + " " + randomString();
78 List<KeyValue<String, Object>> templateParams = Lists.newArrayList( 74 List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
79 new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); 75 new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
80 -  
81 // mock 方法 76 // mock 方法
82 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) 77 httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
83 - .thenReturn(  
84 - "{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"  
85 - ); 78 + .thenReturn("{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}");
86 // 调用 79 // 调用
87 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, 80 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
88 apiTemplateId, templateParams); 81 apiTemplateId, templateParams);
89 // 断言 82 // 断言
90 assertFalse(result.getSuccess()); 83 assertFalse(result.getSuccess());
  84 + assertEquals("BadToken", result.getApiCode());
  85 + assertEquals("Your authorization token is invalid", result.getApiMsg());
  86 + assertEquals("etziWcJFo1C8Ne8X", result.getApiRequestId());
91 } 87 }
92 } 88 }
93 89
@@ -125,4 +121,15 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -125,4 +121,15 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
125 assertEquals(123, statuses.getFirst().getLogId()); 121 assertEquals(123, statuses.getFirst().getLogId());
126 } 122 }
127 123
  124 + @Test
  125 + public void testConvertSmsTemplateAuditStatus() {
  126 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
  127 + smsClient.convertSmsTemplateAuditStatus("passed"));
  128 + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
  129 + smsClient.convertSmsTemplateAuditStatus("reviewing"));
  130 + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
  131 + smsClient.convertSmsTemplateAuditStatus("rejected"));
  132 + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("unknown"),
  133 + "未知审核状态(3)");
  134 + }
128 } 135 }
129 \ No newline at end of file 136 \ 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
@@ -116,7 +116,6 @@ public class SmsClientTests { @@ -116,7 +116,6 @@ public class SmsClientTests {
116 @Test 116 @Test
117 @Disabled 117 @Disabled
118 public void testQiniuSmsClient_sendSms() throws Throwable { 118 public void testQiniuSmsClient_sendSms() throws Throwable {
119 -  
120 SmsChannelProperties properties = new SmsChannelProperties() 119 SmsChannelProperties properties = new SmsChannelProperties()
121 .setApiKey("SMS_QINIU_ACCESS_KEY") 120 .setApiKey("SMS_QINIU_ACCESS_KEY")
122 .setApiSecret("SMS_QINIU_SECRET_KEY"); 121 .setApiSecret("SMS_QINIU_SECRET_KEY");
@@ -135,7 +134,6 @@ public class SmsClientTests { @@ -135,7 +134,6 @@ public class SmsClientTests {
135 @Test 134 @Test
136 @Disabled 135 @Disabled
137 public void testQiniuSmsClient_getSmsTemplate() throws Throwable { 136 public void testQiniuSmsClient_getSmsTemplate() throws Throwable {
138 -  
139 SmsChannelProperties properties = new SmsChannelProperties() 137 SmsChannelProperties properties = new SmsChannelProperties()
140 .setApiKey("SMS_QINIU_ACCESS_KEY") 138 .setApiKey("SMS_QINIU_ACCESS_KEY")
141 .setApiSecret("SMS_QINIU_SECRET_KEY"); 139 .setApiSecret("SMS_QINIU_SECRET_KEY");