Commit 8cce737523806c3bf4a1aa65a716664c032460f9

Authored by YunaiV
2 parents 126aa1b4 efc5ea23

Merge branch 'develop' of https://gitee.com/scholarli/ruoyi-vue-pro_1 into develop

# Conflicts:
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
@@ -4,16 +4,17 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,16 +4,17 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; 5 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
6 import cn.iocoder.yudao.module.system.service.sms.SmsSendService; 6 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
  7 +import com.xingyuv.captcha.util.StreamUtils;
7 import io.swagger.v3.oas.annotations.Operation; 8 import io.swagger.v3.oas.annotations.Operation;
8 import io.swagger.v3.oas.annotations.tags.Tag; 9 import io.swagger.v3.oas.annotations.tags.Tag;
9 -import org.springframework.web.bind.annotation.PostMapping;  
10 -import org.springframework.web.bind.annotation.RequestMapping;  
11 -import org.springframework.web.bind.annotation.RestController; 10 +import org.springframework.web.bind.annotation.*;
12 11
13 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
14 import jakarta.annotation.security.PermitAll; 13 import jakarta.annotation.security.PermitAll;
15 import jakarta.servlet.http.HttpServletRequest; 14 import jakarta.servlet.http.HttpServletRequest;
16 15
  16 +import java.nio.charset.Charset;
  17 +
17 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
18 19
19 @Tag(name = "管理后台 - 短信回调") 20 @Tag(name = "管理后台 - 短信回调")
@@ -26,7 +27,7 @@ public class SmsCallbackController { @@ -26,7 +27,7 @@ public class SmsCallbackController {
26 27
27 @PostMapping("/aliyun") 28 @PostMapping("/aliyun")
28 @PermitAll 29 @PermitAll
29 - @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/zh/sms/developer-reference/configure-delivery-receipts-1 文档") 30 + @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
30 public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { 31 public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
31 String text = ServletUtils.getBody(request); 32 String text = ServletUtils.getBody(request);
32 smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text); 33 smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
@@ -35,7 +36,7 @@ public class SmsCallbackController { @@ -35,7 +36,7 @@ public class SmsCallbackController {
35 36
36 @PostMapping("/tencent") 37 @PostMapping("/tencent")
37 @PermitAll 38 @PermitAll
38 - @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/59178 文档") 39 + @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
39 public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable { 40 public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
40 String text = ServletUtils.getBody(request); 41 String text = ServletUtils.getBody(request);
41 smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text); 42 smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
@@ -46,9 +47,8 @@ public class SmsCallbackController { @@ -46,9 +47,8 @@ public class SmsCallbackController {
46 @PostMapping("/huawei") 47 @PostMapping("/huawei")
47 @PermitAll 48 @PermitAll
48 @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") 49 @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档")
49 - public CommonResult<Boolean> receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable {  
50 - String text = ServletUtils.getBody(request);  
51 - smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); 50 + public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable {
  51 + smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody);
52 return success(true); 52 return success(true);
53 } 53 }
54 54
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
@@ -4,12 +4,11 @@ import cn.hutool.core.lang.Assert; @@ -4,12 +4,11 @@ import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 5
6 import cn.hutool.crypto.SecureUtil; 6 import cn.hutool.crypto.SecureUtil;
7 -import cn.hutool.http.HttpRequest;  
8 -import cn.hutool.http.HttpResponse;  
9 import cn.hutool.json.JSONObject; 7 import cn.hutool.json.JSONObject;
10 import cn.hutool.json.JSONUtil; 8 import cn.hutool.json.JSONUtil;
11 import cn.iocoder.yudao.framework.common.core.KeyValue; 9 import cn.iocoder.yudao.framework.common.core.KeyValue;
12 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  11 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
13 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 12 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; 13 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
15 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; 14 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -17,23 +16,20 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateR @@ -17,23 +16,20 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateR
17 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; 16 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
18 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; 17 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
19 18
20 -import com.fasterxml.jackson.annotation.JsonFormat;  
21 -import com.fasterxml.jackson.annotation.JsonProperty;  
22 -import lombok.Data;  
23 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
24 20
25 import java.io.UnsupportedEncodingException; 21 import java.io.UnsupportedEncodingException;
  22 +import java.net.URLDecoder;
26 import java.net.URLEncoder; 23 import java.net.URLEncoder;
27 import java.text.SimpleDateFormat; 24 import java.text.SimpleDateFormat;
  25 +import java.time.Instant;
  26 +import java.time.ZoneId;
28 import java.util.*; 27 import java.util.*;
29 28
30 29
31 import java.time.LocalDateTime; 30 import java.time.LocalDateTime;
32 31
33 import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; 32 import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
34 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
35 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
36 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;  
37 33
38 // todo @scholar:参考阿里云在优化下 34 // todo @scholar:参考阿里云在优化下
39 /** 35 /**
@@ -45,9 +41,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE @@ -45,9 +41,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
45 @Slf4j 41 @Slf4j
46 public class HuaweiSmsClient extends AbstractSmsClient { 42 public class HuaweiSmsClient extends AbstractSmsClient {
47 43
48 - /**  
49 - * 调用成功 code  
50 - */  
51 public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI 44 public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI
52 public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; 45 public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
53 public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; 46 public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
@@ -76,13 +69,14 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -76,13 +69,14 @@ public class HuaweiSmsClient extends AbstractSmsClient {
76 69
77 List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue())); 70 List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
78 71
79 - JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack);  
80 - SmsResponse smsResponse = getSmsSendResponse(JsonResponse); 72 + JSONObject JsonResponse = request(sendLogId,sender,mobile,templateId,templateParas,statusCallBack);
81 73
82 - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); 74 + return new SmsSendRespDTO().setSuccess("000000".equals(JsonResponse.getStr("code")))
  75 + .setSerialNo(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("smsMsgId"))
  76 + .setApiCode(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("status"));
83 } 77 }
84 78
85 - JSONObject sendSmsRequest(String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException { 79 + JSONObject request(Long sendLogId,String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
86 80
87 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); 81 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
88 sdf.setTimeZone(TimeZone.getTimeZone("UTC")); 82 sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -95,8 +89,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -95,8 +89,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
95 String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" 89 String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
96 + "host:"+ HOST +"\n" 90 + "host:"+ HOST +"\n"
97 + "x-sdk-date:" + sdkDate + "\n"; 91 + "x-sdk-date:" + sdkDate + "\n";
98 - //请求Body,不携带签名名称时,signature请填null  
99 - String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null); 92 + String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, sendLogId);
100 if (null == body || body.isEmpty()) { 93 if (null == body || body.isEmpty()) {
101 return null; 94 return null;
102 } 95 }
@@ -116,26 +109,29 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -116,26 +109,29 @@ public class HuaweiSmsClient extends AbstractSmsClient {
116 + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature; 109 + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature;
117 110
118 // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* 111 // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
119 - HttpResponse response = HttpRequest.post(URL)  
120 - .header("Content-Type", "application/x-www-form-urlencoded")  
121 - .header("X-Sdk-Date", sdkDate)  
122 - .header("host",HOST)  
123 - .header("Authorization", authorization)  
124 - .body(body)  
125 - .execute();  
126 -  
127 - return JSONUtil.parseObj(response.body());  
128 - }  
129 -  
130 - private SmsResponse getSmsSendResponse(JSONObject resJson) {  
131 - SmsResponse smsResponse = new SmsResponse();  
132 - smsResponse.setSuccess("000000".equals(resJson.getStr("code")));  
133 - smsResponse.setData(resJson);  
134 - return smsResponse; 112 + TreeMap<String, String> headers = new TreeMap<>();
  113 + headers.put("Content-Type", "application/x-www-form-urlencoded");
  114 + headers.put("X-Sdk-Date", sdkDate);
  115 + headers.put("host", HOST);
  116 + headers.put("Authorization", authorization);
  117 +
  118 + String responseBody = HttpUtils.post(URL, headers, body);
  119 + return JSONUtil.parseObj(responseBody);
  120 +//
  121 +//
  122 +// HttpResponse response = HttpRequest.post(URL)
  123 +// .header("Content-Type", "application/x-www-form-urlencoded")
  124 +// .header("X-Sdk-Date", sdkDate)
  125 +// .header("host",HOST)
  126 +// .header("Authorization", authorization)
  127 +// .body(body)
  128 +// .execute();
  129 +//
  130 +// return JSONUtil.parseObj(response.body());
135 } 131 }
136 132
137 static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas, 133 static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
138 - String statusCallBack, String signature) throws UnsupportedEncodingException { 134 + String statusCallBack, Long sendLogId) throws UnsupportedEncodingException {
139 if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() 135 if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
140 || templateId.isEmpty()) { 136 || templateId.isEmpty()) {
141 System.out.println("buildRequestBody(): sender, receiver or templateId is null."); 137 System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
@@ -148,7 +144,9 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -148,7 +144,9 @@ public class HuaweiSmsClient extends AbstractSmsClient {
148 appendToBody(body, "&templateId=", templateId); 144 appendToBody(body, "&templateId=", templateId);
149 appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas)); 145 appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
150 appendToBody(body, "&statusCallback=", statusCallBack); 146 appendToBody(body, "&statusCallback=", statusCallBack);
151 - appendToBody(body, "&signature=", signature); 147 + appendToBody(body, "&signature=", null);
  148 + appendToBody(body, "&extend=", String.valueOf(sendLogId));
  149 +
152 return body.toString(); 150 return body.toString();
153 } 151 }
154 152
@@ -158,12 +156,35 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -158,12 +156,35 @@ public class HuaweiSmsClient extends AbstractSmsClient {
158 } 156 }
159 } 157 }
160 @Override 158 @Override
161 - public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {  
162 - List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);  
163 - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(),"DELIVRD"))  
164 - .setErrorCode(status.getStatus()).setErrorMsg(status.getStatus())  
165 - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getUpdateTime())  
166 - .setSerialNo(status.getSmsMsgId())); 159 + public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String requestBody) {
  160 +
  161 + System.out.println("text in parseSmsReceiveStatus===== " + requestBody);
  162 +
  163 + Map<String, String> params = new HashMap<>();
  164 + try {
  165 + String[] pairs = requestBody.split("&");
  166 + for (String pair : pairs) {
  167 + int idx = pair.indexOf("=");
  168 + String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8");
  169 + String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8");
  170 + params.put(key, value);
  171 + }
  172 + } catch (Exception e) {
  173 + e.printStackTrace();
  174 + }
  175 +
  176 + List<SmsReceiveRespDTO> respDTOS = new ArrayList<>();
  177 + respDTOS.add(new SmsReceiveRespDTO()
  178 + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功
  179 + .setErrorCode(params.get("status")) // 状态报告编码
  180 + .setErrorMsg(params.get("statusDesc"))
  181 + .setMobile(params.get("to")) // 手机号
  182 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间
  183 + .setSerialNo(params.get("smsMsgId")) // 发送序列号
  184 + .setLogId(Long.valueOf(params.get("extend")))//logId
  185 + );
  186 +
  187 + return respDTOS;
167 } 188 }
168 189
169 @Override 190 @Override
@@ -171,56 +192,5 @@ public class HuaweiSmsClient extends AbstractSmsClient { @@ -171,56 +192,5 @@ public class HuaweiSmsClient extends AbstractSmsClient {
171 //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 192 //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。
172 return new SmsTemplateRespDTO().setId(null).setContent(null) 193 return new SmsTemplateRespDTO().setId(null).setContent(null)
173 .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); 194 .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
174 -  
175 } 195 }
176 -  
177 - @Data  
178 - public static class SmsResponse {  
179 -  
180 - /**  
181 - * 是否成功  
182 - */  
183 - private boolean success;  
184 -  
185 - /**  
186 - * 厂商原返回体  
187 - */  
188 - private Object data;  
189 -  
190 - }  
191 -  
192 -  
193 - /**  
194 - * 短信接收状态  
195 - *  
196 - * 参见 <a href="https://support.huaweicloud.com/api-msgsms/sms_05_0003.html">文档</a>  
197 - *  
198 - * @author scholar  
199 - */  
200 - @Data  
201 - public static class SmsReceiveStatus {  
202 -  
203 - /**  
204 - * 本条状态报告对应的短信的接收方号码,仅当状态报告中携带了extend参数时才会同时携带该参数  
205 - */  
206 - @JsonProperty("to")  
207 - private String phoneNumber;  
208 -  
209 - /**  
210 - * 短信资源的更新时间,通常为短信平台接收短信状态报告的时间  
211 - */  
212 - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)  
213 - private LocalDateTime updateTime;  
214 -  
215 - /**  
216 - * 短信状态报告枚举值  
217 - */  
218 - private String status;  
219 -  
220 - /**  
221 - * 发送短信成功时返回的短信唯一标识。  
222 - */  
223 - private String smsMsgId;  
224 - }  
225 -  
226 -} 196 +}
227 \ No newline at end of file 197 \ 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/HuaweiSmsClientTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  6 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
  7 +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
  8 +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
  9 +import com.google.common.collect.Lists;
  10 +
  11 +import org.junit.jupiter.api.Test;
  12 +import org.mockito.InjectMocks;
  13 +import org.mockito.MockedStatic;
  14 +
  15 +import java.time.LocalDateTime;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  19 +import static org.junit.jupiter.api.Assertions.*;
  20 +import static org.mockito.ArgumentMatchers.*;
  21 +import static org.mockito.ArgumentMatchers.anyString;
  22 +import static org.mockito.Mockito.mockStatic;
  23 +
  24 +/**
  25 + * {@link HuaweiSmsClient} 的单元测试
  26 + *
  27 + * @author scholar
  28 + */
  29 +public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
  30 +
  31 + private final SmsChannelProperties properties = new SmsChannelProperties()
  32 + .setApiKey(randomString())// 随机一个 apiKey,避免构建报错
  33 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
  34 + .setSignature("芋道源码");
  35 +
  36 + @InjectMocks
  37 + private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties);
  38 +
  39 + @Test
  40 + public void testDoInit() {
  41 + // 调用
  42 + smsClient.doInit();
  43 + }
  44 +
  45 + @Test
  46 + public void testDoSendSms_success() throws Throwable {
  47 +
  48 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  49 + // 准备参数
  50 + Long sendLogId = randomLongId();
  51 + String mobile = randomString();
  52 + String apiTemplateId = randomString() + " " + randomString();
  53 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  54 + new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
  55 +
  56 + // mock 方法
  57 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
  58 + .thenReturn(
  59 + "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"000000\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"000000\",\"description\":\"Success\"}\n"
  60 + );
  61 +
  62 + // 调用
  63 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
  64 + apiTemplateId, templateParams);
  65 + // 断言
  66 + assertTrue(result.getSuccess());
  67 + assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
  68 + assertEquals("000000", result.getApiCode());
  69 +
  70 + }
  71 + }
  72 +
  73 + @Test
  74 + public void testDoSendSms_fail() throws Throwable {
  75 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  76 + // 准备参数
  77 + Long sendLogId = randomLongId();
  78 + String mobile = randomString();
  79 + String apiTemplateId = randomString() + " " + randomString();
  80 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  81 + new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
  82 +
  83 + // mock 方法
  84 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
  85 + .thenReturn(
  86 + "{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"E200015\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"E000000\",\"description\":\"Success\"}\n"
  87 + );
  88 +
  89 + // 调用
  90 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
  91 + apiTemplateId, templateParams);
  92 + // 断言
  93 + assertFalse(result.getSuccess());
  94 + assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
  95 + assertEquals("E200015", result.getApiCode());
  96 + }
  97 + }
  98 +
  99 + @Test
  100 + public void testParseSmsReceiveStatus() {
  101 + // 准备参数
  102 + String text = "sequence=1&total=1&statusDesc=%E7%94%A8%E6%88%B7%E5%B7%B2%E6%88%90%E5%8A%9F%E6%94%B6%E5%88%B0%E7%9F%AD%E4%BF%A1&updateTime=2024-08-15T03%3A00%3A34Z&source=2&smsMsgId=70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459&status=DELIVRD&extend=176";
  103 +
  104 + // 调用
  105 + List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
  106 + // 断言
  107 + assertEquals(1, statuses.size());
  108 + assertTrue(statuses.getFirst().getSuccess());
  109 + assertEquals("DELIVRD", statuses.getFirst().getErrorCode());
  110 + assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), statuses.getFirst().getReceiveTime());
  111 + assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", statuses.getFirst().getSerialNo());
  112 + }
  113 +
  114 +}