【代码优化】SYSTEM:七牛云短信的接入

This commit is contained in:
YunaiV 2024-08-31 08:53:05 +08:00
parent d745a1832d
commit d6ecc032c2
5 changed files with 60 additions and 64 deletions

View File

@ -1,11 +1,9 @@
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.CollStreamUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil; 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.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.HmacAlgorithm;
@ -22,6 +20,9 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.*; import java.util.*;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/** /**
* 七牛云短信客户端的实现类 * 七牛云短信客户端的实现类
@ -34,19 +35,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
private static final String HOST = "sms.qiniuapi.com"; private static final String HOST = "sms.qiniuapi.com";
private static final String PATH = "/v1/message/single";
private static final String TEMPLATE_PATH = "/v1/template";
public QiniuSmsClient(SmsChannelProperties properties) { public QiniuSmsClient(SmsChannelProperties properties) {
super(properties); super(properties);
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
} }
protected void doInit() {
}
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. 执行请求
@ -56,16 +50,16 @@ public class QiniuSmsClient extends AbstractSmsClient {
body.put("mobile", mobile); body.put("mobile", mobile);
body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); 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, "/v1/message/single");
JSONObject response = request("POST", body, PATH);
// 2. 解析请求 // 2. 解析请求
if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败 if (ObjectUtil.isNotEmpty(response.getStr("error"))) {
// 短信请求失败
return new SmsSendRespDTO().setSuccess(false) return new SmsSendRespDTO().setSuccess(false)
.setApiCode(response.getStr("error")) .setApiCode(response.getStr("error"))
.setApiRequestId(response.getStr("request_id")) .setApiRequestId(response.getStr("request_id"))
.setApiMsg(response.getStr("message")); .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"));
} }
@ -81,62 +75,66 @@ public class QiniuSmsClient extends AbstractSmsClient {
*/ */
private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, String path) { 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 signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'");
//请求头 // 1. 请求头
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", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); header.put("Authorization", getSignature(httpMethod, 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 =""; // 2. 发起请求
if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求 String responseBody;
if (Objects.equals(httpMethod, "POST")){
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 {
responseBody = HttpUtils.get("https://" + HOST + path, header); responseBody = HttpUtils.get("https://" + HOST + path, header);
} }
return JSONUtil.parseObj(responseBody); return JSONUtil.parseObj(responseBody);
} }
public String getSignature(String method, String host, String path, String body, String signDate) { private String getSignature(String method, 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); .append("\nHost: ").append(HOST)
dataToSign.append("\n").append("Content-Type").append(": ").append("application/json"); .append("\n").append("Content-Type").append(": ").append("application/json")
dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate); .append("\n").append("X-Qiniu-Date").append(": ").append(signDate)
dataToSign.append("\n\n"); .append("\n\n");
if (ObjectUtil.isNotEmpty(body)) { if (ObjectUtil.isNotEmpty(body)) {
dataToSign.append(body); dataToSign.append(body);
} }
String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true); String signature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret())
.digestBase64(dataToSign.toString(), true);
return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; return "Qiniu " + properties.getApiKey() + ":" + signature;
} }
@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 convertList(status.getJSONArray("items"), new Function<Object, SmsReceiveRespDTO>() {
.setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功
.setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status")) @Override
.setMobile(status.getJSONArray("items").getJSONObject(0).getStr("mobile")) // 手机号 public SmsReceiveRespDTO apply(Object item) {
.setReceiveTime(LocalDateTimeUtil.of(status.getJSONArray("items").getJSONObject(0).getLong("delivrd_at")*1000L)) JSONObject statusObj = (JSONObject) item;
.setSerialNo(status.getJSONArray("items").getJSONObject(0).getStr("message_id")) // 发送序列号 return new SmsReceiveRespDTO()
.setLogId(Long.valueOf(status.getJSONArray("items").getJSONObject(0).getStr("seq")))); // logId .setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功
.setErrorMsg(statusObj.getStr("status")) // 状态报告编码
.setMobile(statusObj.getStr("mobile")) // 手机号
.setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间
.setSerialNo(statusObj.getStr("message_id")) // 发送序列号
.setLogId(statusObj.getLong("seq")); // 用户序列号
}
});
} }
@Override @Override
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
JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId); JSONObject response = request("GET", null, "/v1/template/" + apiTemplateId);
// 2.1 请求失败
if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) {
log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
return null;
}
// 2.2 请求成功 // 2.2 解析请求
return new SmsTemplateRespDTO() return new SmsTemplateRespDTO()
.setId(response.getStr("id")) .setId(response.getStr("id"))
.setContent(response.getStr("template")) .setContent(response.getStr("template"))
@ -146,12 +144,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
@VisibleForTesting @VisibleForTesting
Integer convertSmsTemplateAuditStatus(String templateStatus) { Integer convertSmsTemplateAuditStatus(String templateStatus) {
return switch (templateStatus) { switch (templateStatus) {
case "passed" -> SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); case "passed": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
case "reviewing" -> SmsTemplateAuditStatusEnum.CHECKING.getStatus(); case "reviewing": return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
case "rejected" -> SmsTemplateAuditStatusEnum.FAIL.getStatus(); case "rejected": return SmsTemplateAuditStatusEnum.FAIL.getStatus();
case null, default -> default:
throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus));
}; }
} }
} }

View File

@ -80,6 +80,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
case TENCENT: return new TencentSmsClient(properties); case TENCENT: return new TencentSmsClient(properties);
case HUAWEI: return new HuaweiSmsClient(properties); case HUAWEI: return new HuaweiSmsClient(properties);
case QINIU: return new QiniuSmsClient(properties);
} }
// 创建失败错误日志 + 抛出异常 // 创建失败错误日志 + 抛出异常
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);

View File

@ -162,7 +162,7 @@ public class TencentSmsClient extends AbstractSmsClient {
* @param body 请求参数 * @param body 请求参数
* @return 请求结果 * @return 请求结果
*/ */
private JSONObject request(String action, TreeMap<String, Object> body) throws Exception { private JSONObject request(String action, TreeMap<String, Object> body) {
// 1.1 请求 Header // 1.1 请求 Header
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json; charset=utf-8"); headers.put("Content-Type", "application/json; charset=utf-8");

View File

@ -29,6 +29,7 @@ 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避免构建报错
@ -37,12 +38,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
@InjectMocks @InjectMocks
private QiniuSmsClient smsClient = new QiniuSmsClient(properties); private QiniuSmsClient smsClient = new QiniuSmsClient(properties);
@Test
public void testDoInit() {
// 调用
smsClient.doInit();
}
@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)) {
@ -113,12 +108,13 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text); List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
// 断言 // 断言
assertEquals(1, statuses.size()); assertEquals(1, statuses.size());
assertTrue(statuses.getFirst().getSuccess()); SmsReceiveRespDTO status = statuses.get(0);
assertEquals("DELIVRD", statuses.getFirst().getErrorMsg()); assertTrue(status.getSuccess());
assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), statuses.getFirst().getReceiveTime()); assertEquals("DELIVRD", status.getErrorMsg());
assertEquals("18881234567", statuses.getFirst().getMobile()); assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), status.getReceiveTime());
assertEquals("10135515063508004167", statuses.getFirst().getSerialNo()); assertEquals("18881234567", status.getMobile());
assertEquals(123, statuses.getFirst().getLogId()); assertEquals("10135515063508004167", status.getSerialNo());
assertEquals(123, status.getLogId());
} }
@Test @Test

View File

@ -1,5 +1,6 @@
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.ListUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
@ -47,7 +48,7 @@ public class SmsClientTests {
String mobile = "15601691323"; String mobile = "15601691323";
String apiTemplateId = "SMS_207945135"; String apiTemplateId = "SMS_207945135";
// 调用 // 调用
SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
// 打印结果 // 打印结果
System.out.println(sendRespDTO); System.out.println(sendRespDTO);
} }
@ -68,7 +69,7 @@ public class SmsClientTests {
String mobile = "15601691323"; String mobile = "15601691323";
String apiTemplateId = "358212"; String apiTemplateId = "358212";
// 调用 // 调用
SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
// 打印结果 // 打印结果
System.out.println(sendRespDTO); System.out.println(sendRespDTO);
} }
@ -105,7 +106,7 @@ public class SmsClientTests {
Long sendLogId = System.currentTimeMillis(); Long sendLogId = System.currentTimeMillis();
String mobile = "17321315478"; String mobile = "17321315478";
String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024")); List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1024"));
// 调用 // 调用
SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
// 打印结果 // 打印结果
@ -125,7 +126,7 @@ public class SmsClientTests {
Long sendLogId = System.currentTimeMillis(); Long sendLogId = System.currentTimeMillis();
String mobile = "17321315478"; String mobile = "17321315478";
String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1122")); List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1122"));
// 调用 // 调用
SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
// 打印结果 // 打印结果