Commit d6ecc032c2ec342522622afe358383eee81f2230

Authored by YunaiV
1 parent d745a183

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

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.CollStreamUtil;
4 -import cn.hutool.core.collection.ListUtil;  
5 import cn.hutool.core.date.DateUtil; 4 import cn.hutool.core.date.DateUtil;
6 import cn.hutool.core.date.LocalDateTimeUtil; 5 import cn.hutool.core.date.LocalDateTimeUtil;
7 import cn.hutool.core.lang.Assert; 6 import cn.hutool.core.lang.Assert;
8 -import cn.hutool.core.util.ObjUtil;  
9 import cn.hutool.core.util.ObjectUtil; 7 import cn.hutool.core.util.ObjectUtil;
10 import cn.hutool.crypto.SecureUtil; 8 import cn.hutool.crypto.SecureUtil;
11 import cn.hutool.crypto.digest.HmacAlgorithm; 9 import cn.hutool.crypto.digest.HmacAlgorithm;
@@ -22,6 +20,9 @@ import com.google.common.annotations.VisibleForTesting; @@ -22,6 +20,9 @@ import com.google.common.annotations.VisibleForTesting;
22 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
23 21
24 import java.util.*; 22 import java.util.*;
  23 +import java.util.function.Function;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
25 26
26 /** 27 /**
27 * 七牛云短信客户端的实现类 28 * 七牛云短信客户端的实现类
@@ -34,19 +35,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -34,19 +35,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
34 35
35 private static final String HOST = "sms.qiniuapi.com"; 36 private static final String HOST = "sms.qiniuapi.com";
36 37
37 - private static final String PATH = "/v1/message/single";  
38 -  
39 - private static final String TEMPLATE_PATH = "/v1/template";  
40 -  
41 public QiniuSmsClient(SmsChannelProperties properties) { 38 public QiniuSmsClient(SmsChannelProperties properties) {
42 super(properties); 39 super(properties);
43 Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); 40 Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
44 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); 41 Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
45 } 42 }
46 43
47 - protected void doInit() {  
48 - }  
49 -  
50 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, 44 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
51 List<KeyValue<String, Object>> templateParams) throws Throwable { 45 List<KeyValue<String, Object>> templateParams) throws Throwable {
52 // 1. 执行请求 46 // 1. 执行请求
@@ -56,16 +50,16 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -56,16 +50,16 @@ public class QiniuSmsClient extends AbstractSmsClient {
56 body.put("mobile", mobile); 50 body.put("mobile", mobile);
57 body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); 51 body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue));
58 body.put("seq", Long.toString(sendLogId)); 52 body.put("seq", Long.toString(sendLogId));
  53 + JSONObject response = request("POST", body, "/v1/message/single");
59 54
60 - JSONObject response = request("POST", body, PATH);  
61 // 2. 解析请求 55 // 2. 解析请求
62 - if (ObjectUtil.isNotEmpty(response.getStr("error"))){//短信请求失败 56 + if (ObjectUtil.isNotEmpty(response.getStr("error"))) {
  57 + // 短信请求失败
63 return new SmsSendRespDTO().setSuccess(false) 58 return new SmsSendRespDTO().setSuccess(false)
64 .setApiCode(response.getStr("error")) 59 .setApiCode(response.getStr("error"))
65 .setApiRequestId(response.getStr("request_id")) 60 .setApiRequestId(response.getStr("request_id"))
66 .setApiMsg(response.getStr("message")); 61 .setApiMsg(response.getStr("message"));
67 } 62 }
68 -  
69 return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) 63 return new SmsSendRespDTO().setSuccess(response.containsKey("message_id"))
70 .setSerialNo(response.getStr("message_id")); 64 .setSerialNo(response.getStr("message_id"));
71 } 65 }
@@ -81,62 +75,66 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -81,62 +75,66 @@ public class QiniuSmsClient extends AbstractSmsClient {
81 */ 75 */
82 private JSONObject request(String httpMethod, LinkedHashMap<String, Object> body, String path) { 76 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'"); 77 String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'");
84 - //请求头 78 + // 1. 请求头
85 Map<String, String> header = new HashMap<>(4); 79 Map<String, String> header = new HashMap<>(4);
86 header.put("HOST", HOST); 80 header.put("HOST", HOST);
87 - header.put("Authorization", getSignature(httpMethod, HOST, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); 81 + header.put("Authorization", getSignature(httpMethod, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate));
88 header.put("Content-Type", "application/json"); 82 header.put("Content-Type", "application/json");
89 header.put("X-Qiniu-Date", signDate); 83 header.put("X-Qiniu-Date", signDate);
90 84
91 - String responseBody ="";  
92 - if (Objects.equals(httpMethod, "POST")){// POST 发送短消息用POST请求 85 + // 2. 发起请求
  86 + String responseBody;
  87 + if (Objects.equals(httpMethod, "POST")){
93 responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); 88 responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body));
94 - }else { // GET 查询template状态用GET请求 89 + } else {
95 responseBody = HttpUtils.get("https://" + HOST + path, header); 90 responseBody = HttpUtils.get("https://" + HOST + path, header);
96 } 91 }
97 return JSONUtil.parseObj(responseBody); 92 return JSONUtil.parseObj(responseBody);
98 } 93 }
99 94
100 - public String getSignature(String method, String host, String path, String body, String signDate) { 95 + private String getSignature(String method, String path, String body, String signDate) {
101 StringBuilder dataToSign = new StringBuilder(); 96 StringBuilder dataToSign = new StringBuilder();
102 - dataToSign.append(method.toUpperCase()).append(" ").append(path);  
103 - dataToSign.append("\nHost: ").append(host);  
104 - dataToSign.append("\n").append("Content-Type").append(": ").append("application/json");  
105 - dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate);  
106 - dataToSign.append("\n\n"); 97 + dataToSign.append(method.toUpperCase()).append(" ").append(path)
  98 + .append("\nHost: ").append(HOST)
  99 + .append("\n").append("Content-Type").append(": ").append("application/json")
  100 + .append("\n").append("X-Qiniu-Date").append(": ").append(signDate)
  101 + .append("\n\n");
107 if (ObjectUtil.isNotEmpty(body)) { 102 if (ObjectUtil.isNotEmpty(body)) {
108 dataToSign.append(body); 103 dataToSign.append(body);
109 } 104 }
110 - String encodedSignature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()).digestBase64(dataToSign.toString(), true);  
111 -  
112 - return "Qiniu " + properties.getApiKey() + ":" + encodedSignature; 105 + String signature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret())
  106 + .digestBase64(dataToSign.toString(), true);
  107 + return "Qiniu " + properties.getApiKey() + ":" + signature;
113 } 108 }
114 109
115 @Override 110 @Override
116 public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) { 111 public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
117 JSONObject status = JSONUtil.parseObj(text); 112 JSONObject status = JSONUtil.parseObj(text);
118 // 字段参考 https://developer.qiniu.com/sms/5910/message-push 113 // 字段参考 https://developer.qiniu.com/sms/5910/message-push
119 - return ListUtil.of(new SmsReceiveRespDTO()  
120 - .setSuccess("DELIVRD".equals(status.getJSONArray("items").getJSONObject(0).getStr("status"))) // 是否接收成功  
121 - .setErrorMsg(status.getJSONArray("items").getJSONObject(0).getStr("status"))  
122 - .setMobile(status.getJSONArray("items").getJSONObject(0).getStr("mobile")) // 手机号  
123 - .setReceiveTime(LocalDateTimeUtil.of(status.getJSONArray("items").getJSONObject(0).getLong("delivrd_at")*1000L))  
124 - .setSerialNo(status.getJSONArray("items").getJSONObject(0).getStr("message_id")) // 发送序列号  
125 - .setLogId(Long.valueOf(status.getJSONArray("items").getJSONObject(0).getStr("seq")))); // logId 114 + return convertList(status.getJSONArray("items"), new Function<Object, SmsReceiveRespDTO>() {
  115 +
  116 + @Override
  117 + public SmsReceiveRespDTO apply(Object item) {
  118 + JSONObject statusObj = (JSONObject) item;
  119 + return new SmsReceiveRespDTO()
  120 + .setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功
  121 + .setErrorMsg(statusObj.getStr("status")) // 状态报告编码
  122 + .setMobile(statusObj.getStr("mobile")) // 手机号
  123 + .setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间
  124 + .setSerialNo(statusObj.getStr("message_id")) // 发送序列号
  125 + .setLogId(statusObj.getLong("seq")); // 用户序列号
  126 + }
  127 +
  128 + });
126 } 129 }
127 130
128 @Override 131 @Override
129 public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { 132 public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
130 // 1. 执行请求 133 // 1. 执行请求
131 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template 134 // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template
132 - JSONObject response = request("GET", null, TEMPLATE_PATH + "/" + apiTemplateId);  
133 - // 2.1 请求失败  
134 - if (ObjUtil.notEqual(response.getStr("audit_status"), "passed")) {  
135 - log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);  
136 - return null;  
137 - } 135 + JSONObject response = request("GET", null, "/v1/template/" + apiTemplateId);
138 136
139 - // 2.2 请求成功 137 + // 2.2 解析请求
140 return new SmsTemplateRespDTO() 138 return new SmsTemplateRespDTO()
141 .setId(response.getStr("id")) 139 .setId(response.getStr("id"))
142 .setContent(response.getStr("template")) 140 .setContent(response.getStr("template"))
@@ -146,12 +144,12 @@ public class QiniuSmsClient extends AbstractSmsClient { @@ -146,12 +144,12 @@ public class QiniuSmsClient extends AbstractSmsClient {
146 144
147 @VisibleForTesting 145 @VisibleForTesting
148 Integer convertSmsTemplateAuditStatus(String templateStatus) { 146 Integer convertSmsTemplateAuditStatus(String templateStatus) {
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 - }; 147 + switch (templateStatus) {
  148 + case "passed": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
  149 + case "reviewing": return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
  150 + case "rejected": return SmsTemplateAuditStatusEnum.FAIL.getStatus();
  151 + default:
  152 + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus));
  153 + }
156 } 154 }
157 } 155 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java
@@ -80,6 +80,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { @@ -80,6 +80,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
80 case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); 80 case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
81 case TENCENT: return new TencentSmsClient(properties); 81 case TENCENT: return new TencentSmsClient(properties);
82 case HUAWEI: return new HuaweiSmsClient(properties); 82 case HUAWEI: return new HuaweiSmsClient(properties);
  83 + case QINIU: return new QiniuSmsClient(properties);
83 } 84 }
84 // 创建失败,错误日志 + 抛出异常 85 // 创建失败,错误日志 + 抛出异常
85 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); 86 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
@@ -162,7 +162,7 @@ public class TencentSmsClient extends AbstractSmsClient { @@ -162,7 +162,7 @@ public class TencentSmsClient extends AbstractSmsClient {
162 * @param body 请求参数 162 * @param body 请求参数
163 * @return 请求结果 163 * @return 请求结果
164 */ 164 */
165 - private JSONObject request(String action, TreeMap<String, Object> body) throws Exception { 165 + private JSONObject request(String action, TreeMap<String, Object> body) {
166 // 1.1 请求 Header 166 // 1.1 请求 Header
167 Map<String, String> headers = new HashMap<>(); 167 Map<String, String> headers = new HashMap<>();
168 headers.put("Content-Type", "application/json; charset=utf-8"); 168 headers.put("Content-Type", "application/json; charset=utf-8");
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.mockStatic; @@ -29,6 +29,7 @@ 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 +
32 private final SmsChannelProperties properties = new SmsChannelProperties() 33 private final SmsChannelProperties properties = new SmsChannelProperties()
33 .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 34 .setApiKey(randomString())// 随机一个 apiKey,避免构建报错
34 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 35 .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
@@ -38,12 +39,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -38,12 +39,6 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
38 private QiniuSmsClient smsClient = new QiniuSmsClient(properties); 39 private QiniuSmsClient smsClient = new QiniuSmsClient(properties);
39 40
40 @Test 41 @Test
41 - public void testDoInit() {  
42 - // 调用  
43 - smsClient.doInit();  
44 - }  
45 -  
46 - @Test  
47 public void testDoSendSms_success() throws Throwable { 42 public void testDoSendSms_success() throws Throwable {
48 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { 43 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
49 // 准备参数 44 // 准备参数
@@ -113,12 +108,13 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest { @@ -113,12 +108,13 @@ public class QiniuSmsClientTest extends BaseMockitoUnitTest {
113 List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text); 108 List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
114 // 断言 109 // 断言
115 assertEquals(1, statuses.size()); 110 assertEquals(1, statuses.size());
116 - assertTrue(statuses.getFirst().getSuccess());  
117 - assertEquals("DELIVRD", statuses.getFirst().getErrorMsg());  
118 - assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), statuses.getFirst().getReceiveTime());  
119 - assertEquals("18881234567", statuses.getFirst().getMobile());  
120 - assertEquals("10135515063508004167", statuses.getFirst().getSerialNo());  
121 - assertEquals(123, statuses.getFirst().getLogId()); 111 + SmsReceiveRespDTO status = statuses.get(0);
  112 + assertTrue(status.getSuccess());
  113 + assertEquals("DELIVRD", status.getErrorMsg());
  114 + assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), status.getReceiveTime());
  115 + assertEquals("18881234567", status.getMobile());
  116 + assertEquals("10135515063508004167", status.getSerialNo());
  117 + assertEquals(123, status.getLogId());
122 } 118 }
123 119
124 @Test 120 @Test
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.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.ListUtil;
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 4 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; 5 import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
5 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; 6 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -47,7 +48,7 @@ public class SmsClientTests { @@ -47,7 +48,7 @@ public class SmsClientTests {
47 String mobile = "15601691323"; 48 String mobile = "15601691323";
48 String apiTemplateId = "SMS_207945135"; 49 String apiTemplateId = "SMS_207945135";
49 // 调用 50 // 调用
50 - SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); 51 + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
51 // 打印结果 52 // 打印结果
52 System.out.println(sendRespDTO); 53 System.out.println(sendRespDTO);
53 } 54 }
@@ -68,7 +69,7 @@ public class SmsClientTests { @@ -68,7 +69,7 @@ public class SmsClientTests {
68 String mobile = "15601691323"; 69 String mobile = "15601691323";
69 String apiTemplateId = "358212"; 70 String apiTemplateId = "358212";
70 // 调用 71 // 调用
71 - SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); 72 + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
72 // 打印结果 73 // 打印结果
73 System.out.println(sendRespDTO); 74 System.out.println(sendRespDTO);
74 } 75 }
@@ -105,7 +106,7 @@ public class SmsClientTests { @@ -105,7 +106,7 @@ public class SmsClientTests {
105 Long sendLogId = System.currentTimeMillis(); 106 Long sendLogId = System.currentTimeMillis();
106 String mobile = "17321315478"; 107 String mobile = "17321315478";
107 String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; 108 String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
108 - List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024")); 109 + List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1024"));
109 // 调用 110 // 调用
110 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); 111 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
111 // 打印结果 112 // 打印结果
@@ -125,7 +126,7 @@ public class SmsClientTests { @@ -125,7 +126,7 @@ public class SmsClientTests {
125 Long sendLogId = System.currentTimeMillis(); 126 Long sendLogId = System.currentTimeMillis();
126 String mobile = "17321315478"; 127 String mobile = "17321315478";
127 String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; 128 String apiTemplateId = "3644cdab863546a3b718d488659a99ef";
128 - List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1122")); 129 + List<KeyValue<String, Object>> templateParams = ListUtil.of(new KeyValue<>("code", "1122"));
129 // 调用 130 // 调用
130 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); 131 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
131 // 打印结果 132 // 打印结果