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

This commit is contained in:
scholar 2024-08-28 10:51:19 +08:00
parent 4f7ac969fe
commit 1c1abae5bb
4 changed files with 51 additions and 63 deletions

View File

@ -135,7 +135,6 @@ public class HttpUtils {
* @return 请求结果 * @return 请求结果
*/ */
public static String post(String url, Map<String, String> headers, String requestBody) { public static String post(String url, Map<String, String> headers, String requestBody) {
try (HttpResponse response = HttpRequest.post(url) try (HttpResponse response = HttpRequest.post(url)
.addHeaders(headers) .addHeaders(headers)
.body(requestBody) .body(requestBody)
@ -154,7 +153,6 @@ public class HttpUtils {
* @return 请求结果 * @return 请求结果
*/ */
public static String get(String url, Map<String, String> headers) { public static String get(String url, Map<String, String> headers) {
try (HttpResponse response = HttpRequest.get(url) try (HttpResponse response = HttpRequest.get(url)
.addHeaders(headers) .addHeaders(headers)
.execute()) { .execute()) {

View File

@ -1,10 +1,13 @@
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; 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.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil; 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.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; 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 com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
/** /**
* 七牛云短信客户端的实现类 * 七牛云短信客户端的实现类
@ -45,69 +44,60 @@ public class QiniuSmsClient extends AbstractSmsClient {
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
} }
@Override
protected void doInit() { protected void doInit() {
} }
@Override
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
List<KeyValue<String, Object>> templateParams) throws Throwable { List<KeyValue<String, Object>> templateParams) throws Throwable {
// 1. 执行请求 // 1. 执行请求
// 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages
LinkedHashMap<String, Object> body = new LinkedHashMap<>(); LinkedHashMap<String, Object> body = new LinkedHashMap<>();
Map<String, Object> paramsMap = templateParams.stream()
.collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue));
body.put("template_id", apiTemplateId); body.put("template_id", apiTemplateId);
body.put("mobile", mobile); body.put("mobile", mobile);
body.put("parameters", paramsMap); body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue));
body.put("seq", Long.toString(sendLogId)); body.put("seq", Long.toString(sendLogId));
JSONObject response = request("POST", body, null); JSONObject response = request("POST", body, PATH);
// 2. 解析请求 // 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")) return new SmsSendRespDTO().setSuccess(response.containsKey("message_id"))
.setSerialNo(response.getStr("message_id")); .setSerialNo(response.getStr("message_id"));
} }
/** /**
* 请求七牛云短信 * 请求七牛云短信
* *
* @see <a href="https://developer.qiniu.com/sms/5842/sms-api-authentication"</> * @see <a href="https://developer.qiniu.com/sms/5842/sms-api-authentication"</>
* @param httpMethod http请求方法 * @param httpMethod http请求方法
* @param queryParams 请求参数 * @param body http请求消息体
* @param path URL path
* @return 请求结果 * @return 请求结果
*/ */
private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, Map<String, Object> queryParams) { private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, String path) {
String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'");
String signature = "";
String templateIdPath = "";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
String signDate = dateFormat.format(new Date());
//请求头 //请求头
Map<String, String> header = new HashMap<>(4); Map<String, String> header = new HashMap<>(4);
header.put("HOST", HOST); 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("Content-Type", "application/json");
header.put("X-Qiniu-Date", signDate); header.put("X-Qiniu-Date", signDate);
String responseBody =""; String responseBody ="";
if(Objects.equals(httpMethod, "POST")){ if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求
header.put("Authorization", getSignature(httpMethod, HOST, PATH, JSONUtil.toJsonStr(body), signDate)); responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body));
responseBody = HttpUtils.post("https://" + HOST + PATH, header, JSONUtil.toJsonStr(body)); }else { // GET 查询template状态用GET请求
}else { // GET responseBody = HttpUtils.get("https://" + HOST + path, header);
templateIdPath = TEMPLATE_PATH + "/" + queryParams.get("template_id");
header.put("Authorization", getSignature(httpMethod, HOST, templateIdPath, null, signDate));
responseBody = HttpUtils.get("https://" + HOST + templateIdPath, header);
} }
return JSONUtil.parseObj(responseBody); return JSONUtil.parseObj(responseBody);
} }
public String getSignature(String method, String host, String path, String body, String signDate) { public String getSignature(String method, String host, String path, String body, String signDate) {
StringBuilder dataToSign = new StringBuilder(); StringBuilder dataToSign = new StringBuilder();
dataToSign.append(method.toUpperCase()).append(" ").append(path); dataToSign.append(method.toUpperCase()).append(" ").append(path);
dataToSign.append("\nHost: ").append(host); dataToSign.append("\nHost: ").append(host);
@ -117,18 +107,15 @@ public class QiniuSmsClient extends AbstractSmsClient {
if (ObjectUtil.isNotEmpty(body)) { if (ObjectUtil.isNotEmpty(body)) {
dataToSign.append(body); dataToSign.append(body);
} }
HMac hMac = new HMac(HmacAlgorithm.HmacSHA1, properties.getApiSecret().getBytes(StandardCharsets.UTF_8)); String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true);
byte[] signData = hMac.digest(dataToSign.toString().getBytes(StandardCharsets.UTF_8));
String encodedSignature = Base64.getEncoder().encodeToString(signData);
return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; return "Qiniu " + properties.getApiKey() + ":" + encodedSignature;
} }
@Override @Override
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) { public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
JSONObject status = JSONUtil.parseObj(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() return ListUtil.of(new SmsReceiveRespDTO()
.setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功 .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功
.setErrorMsg(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 { public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
// 1. 执行请求 // 1. 执行请求
// 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template
HashMap<String, Object> queryParam = new HashMap<>(); JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId);
queryParam.put("template_id", apiTemplateId);
JSONObject response = request("GET", null, queryParam);
// 2.1 请求失败 // 2.1 请求失败
String status = response.getStr("audit_status"); if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) {
if (!Objects.equals(status, "passed")) {
log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
return null; return null;
} }
// 2.2 请求成功 // 2.2 请求成功
return new SmsTemplateRespDTO() return new SmsTemplateRespDTO()
.setId(response.getStr("id")) .setId(response.getStr("id"))
@ -162,11 +146,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
@VisibleForTesting @VisibleForTesting
Integer convertSmsTemplateAuditStatus(String templateStatus) { Integer convertSmsTemplateAuditStatus(String templateStatus) {
return switch (templateStatus) {
if(Objects.equals(templateStatus, "passed")){ case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus();
}else { case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus();
throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); case null, default ->
} throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus));
};
} }
} }

View File

@ -29,7 +29,6 @@ import static org.mockito.Mockito.mockStatic;
* @author scholar * @author scholar
*/ */
public class QiniuSmsClientTest extends BaseMockitoUnitTest { public class QiniuSmsClientTest extends BaseMockitoUnitTest {
private final SmsChannelProperties properties = new SmsChannelProperties() private final SmsChannelProperties properties = new SmsChannelProperties()
.setApiKey(randomString())// 随机一个 apiKey避免构建报错 .setApiKey(randomString())// 随机一个 apiKey避免构建报错
.setApiSecret(randomString()) // 随机一个 apiSecret避免构建报错 .setApiSecret(randomString()) // 随机一个 apiSecret避免构建报错
@ -46,7 +45,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
@Test @Test
public void testDoSendSms_success() throws Throwable { public void testDoSendSms_success() throws Throwable {
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
// 准备参数 // 准备参数
Long sendLogId = randomLongId(); Long sendLogId = randomLongId();
@ -56,9 +54,7 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
// mock 方法 // mock 方法
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
.thenReturn( .thenReturn("{\"message_id\":\"17245678901\"}");
"{\"message_id\":\"17245678901\"}"
);
// 调用 // 调用
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
apiTemplateId, templateParams); apiTemplateId, templateParams);
@ -77,17 +73,17 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
String apiTemplateId = randomString() + " " + randomString(); String apiTemplateId = randomString() + " " + randomString();
List<KeyValue<String, Object>> templateParams = Lists.newArrayList( List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
// mock 方法 // mock 方法
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
.thenReturn( .thenReturn("{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}");
"{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"
);
// 调用 // 调用
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
apiTemplateId, templateParams); apiTemplateId, templateParams);
// 断言 // 断言
assertFalse(result.getSuccess()); 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()); 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)");
}
} }

View File

@ -116,7 +116,6 @@ public class SmsClientTests {
@Test @Test
@Disabled @Disabled
public void testQiniuSmsClient_sendSms() throws Throwable { public void testQiniuSmsClient_sendSms() throws Throwable {
SmsChannelProperties properties = new SmsChannelProperties() SmsChannelProperties properties = new SmsChannelProperties()
.setApiKey("SMS_QINIU_ACCESS_KEY") .setApiKey("SMS_QINIU_ACCESS_KEY")
.setApiSecret("SMS_QINIU_SECRET_KEY"); .setApiSecret("SMS_QINIU_SECRET_KEY");
@ -135,7 +134,6 @@ public class SmsClientTests {
@Test @Test
@Disabled @Disabled
public void testQiniuSmsClient_getSmsTemplate() throws Throwable { public void testQiniuSmsClient_getSmsTemplate() throws Throwable {
SmsChannelProperties properties = new SmsChannelProperties() SmsChannelProperties properties = new SmsChannelProperties()
.setApiKey("SMS_QINIU_ACCESS_KEY") .setApiKey("SMS_QINIU_ACCESS_KEY")
.setApiSecret("SMS_QINIU_SECRET_KEY"); .setApiSecret("SMS_QINIU_SECRET_KEY");