Commit 5b7e637ebf9c0419ffd0516043af33210c3ed0b3

Authored by YunaiV
1 parent 8cce7375

【功能优化】短信:华为云的实现优化

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5 5 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
6 6 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
7   -import com.xingyuv.captcha.util.StreamUtils;
8 7 import io.swagger.v3.oas.annotations.Operation;
9 8 import io.swagger.v3.oas.annotations.tags.Tag;
10 9 import org.springframework.web.bind.annotation.*;
... ... @@ -13,8 +12,6 @@ import jakarta.annotation.Resource;
13 12 import jakarta.annotation.security.PermitAll;
14 13 import jakarta.servlet.http.HttpServletRequest;
15 14  
16   -import java.nio.charset.Charset;
17   -
18 15 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19 16  
20 17 @Tag(name = "管理后台 - 短信回调")
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java
... ... @@ -26,15 +26,9 @@ public abstract class AbstractSmsClient implements SmsClient {
26 26 * 初始化
27 27 */
28 28 public final void init() {
29   - doInit();
30 29 log.debug("[init][配置({}) 初始化完成]", properties);
31 30 }
32 31  
33   - /**
34   - * 自定义初始化
35   - */
36   - protected abstract void doInit();
37   -
38 32 public final void refresh(SmsChannelProperties properties) {
39 33 // 判断是否更新
40 34 if (properties.equals(this.properties)) {
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
... ... @@ -51,10 +51,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
51 51 }
52 52  
53 53 @Override
54   - protected void doInit() {
55   - }
56   -
57   - @Override
58 54 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
59 55 List<KeyValue<String, Object>> templateParams) throws Throwable {
60 56 Assert.notBlank(properties.getSignature(), "短信签名不能为空");
... ... @@ -80,7 +76,7 @@ public class AliyunSmsClient extends AbstractSmsClient {
80 76 @Override
81 77 public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
82 78 JSONArray statuses = JSONUtil.parseArray(text);
83   - // 字段参考
  79 + // 字段参考 https://help.aliyun.com/zh/sms/developer-reference/smsreport-2
84 80 return convertList(statuses, status -> {
85 81 JSONObject statusObj = (JSONObject) status;
86 82 return new SmsReceiveRespDTO()
... ... @@ -166,7 +162,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
166 162 String hashedRequestBody = DigestUtil.sha256Hex(requestBody);
167 163  
168 164 // 4. 构建 Authorization 签名
169   - String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
  165 + String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n"
  166 + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
170 167 String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
171 168 String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
172 169 String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java
... ... @@ -37,10 +37,6 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
37 37 }
38 38  
39 39 @Override
40   - protected void doInit() {
41   - }
42   -
43   - @Override
44 40 public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
45 41 String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
46 42 // 构建请求
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
1 1 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
  4 +import cn.hutool.core.date.format.FastDateFormat;
3 5 import cn.hutool.core.lang.Assert;
  6 +import cn.hutool.core.util.CharsetUtil;
4 7 import cn.hutool.core.util.StrUtil;
5   -
6 8 import cn.hutool.crypto.SecureUtil;
  9 +import cn.hutool.http.HttpUtil;
7 10 import cn.hutool.json.JSONObject;
8 11 import cn.hutool.json.JSONUtil;
9 12 import cn.iocoder.yudao.framework.common.core.KeyValue;
10   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
11 13 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
12 14 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
13 15 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
... ... @@ -15,23 +17,19 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD
15 17 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
16 18 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
17 19 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
18   -
19 20 import lombok.extern.slf4j.Slf4j;
20 21  
21 22 import java.io.UnsupportedEncodingException;
22   -import java.net.URLDecoder;
23 23 import java.net.URLEncoder;
24   -import java.text.SimpleDateFormat;
  24 +import java.nio.charset.StandardCharsets;
25 25 import java.time.Instant;
  26 +import java.time.LocalDateTime;
26 27 import java.time.ZoneId;
27 28 import java.util.*;
28 29  
29   -
30   -import java.time.LocalDateTime;
31   -
32 30 import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
33 32  
34   -// todo @scholar:参考阿里云在优化下
35 33 /**
36 34 * 华为短信客户端的实现类
37 35 *
... ... @@ -41,13 +39,11 @@ import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
41 39 @Slf4j
42 40 public class HuaweiSmsClient extends AbstractSmsClient {
43 41  
44   - public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI
45   - public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
46   - public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
  42 + private static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI
  43 + private static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
  44 + private static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
47 45  
48   - @Override
49   - protected void doInit() {
50   - }
  46 + private static final String RESPONSE_CODE_SUCCESS = "000000";
51 47  
52 48 public HuaweiSmsClient(SmsChannelProperties properties) {
53 49 super(properties);
... ... @@ -58,139 +54,96 @@ public class HuaweiSmsClient extends AbstractSmsClient {
58 54 @Override
59 55 public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
60 56 List<KeyValue<String, Object>> templateParams) throws Throwable {
  57 + // 1. 执行请求
61 58 // 参考链接 https://support.huaweicloud.com/api-msgsms/sms_05_0001.html
62   - // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构
63   - // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。
64   - String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
65   - String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID
66   -
67   - //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
  59 + // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构,
  60 + // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符)
  61 + String sender = apiTemplateId.split(" ")[1]; // 中国大陆短信签名通道号或全球短信通道号
  62 + String templateId = apiTemplateId.split(" ")[0]; //模板ID
68 63 String statusCallBack = properties.getCallbackUrl();
69   -
70   - List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
71   -
72   - JSONObject JsonResponse = request(sendLogId,sender,mobile,templateId,templateParas,statusCallBack);
73   -
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"));
77   - }
78   -
79   - JSONObject request(Long sendLogId,String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
80   -
81   - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
82   - sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
83   - String sdkDate = sdf.format(new Date());
84   -
85   - // ************* 步骤 1:拼接规范请求串 *************
86   - String httpRequestMethod = "POST";
87   - String canonicalUri = "/sms/batchSendSms/v1/";
88   - String canonicalQueryString = "";//查询参数为空
89   - String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
90   - + "host:"+ HOST +"\n"
91   - + "x-sdk-date:" + sdkDate + "\n";
92   - String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, sendLogId);
93   - if (null == body || body.isEmpty()) {
94   - return null;
  64 + StringBuilder requestBody = new StringBuilder();
  65 + appendToBody(requestBody, "from=", sender);
  66 + appendToBody(requestBody, "&to=", mobile);
  67 + appendToBody(requestBody, "&templateId=", templateId);
  68 + appendToBody(requestBody, "&templateParas=", JsonUtils.toJsonString(
  69 + convertList(templateParams, kv -> String.valueOf(kv.getValue()))));
  70 + appendToBody(requestBody, "&statusCallback=", statusCallBack);
  71 + appendToBody(requestBody, "&extend=", String.valueOf(sendLogId));
  72 + JSONObject response = request("/sms/batchSendSms/v1/", "POST", requestBody.toString());
  73 +
  74 + // 2. 解析请求
  75 + if (!response.containsKey("result")) { // 例如说:密钥不正确
  76 + return new SmsSendRespDTO().setSuccess(false)
  77 + .setApiCode(response.getStr("code"))
  78 + .setApiMsg(response.getStr("description"));
95 79 }
96   - String hashedRequestBody = sha256Hex(body);
97   - String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
98   - + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + hashedRequestBody;
99   -
100   - // ************* 步骤 2:拼接待签名字符串 *************
101   - String hashedCanonicalRequest = sha256Hex(canonicalRequest);
102   - String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + hashedCanonicalRequest;
103   -
104   - // ************* 步骤 3:计算签名 *************
105   - String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign);
106   -
107   - // ************* 步骤 4:拼接 Authorization *************
108   - String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", "
109   - + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature;
  80 + JSONObject sendResult = response.getJSONArray("result").getJSONObject(0);
  81 + return new SmsSendRespDTO().setSuccess(RESPONSE_CODE_SUCCESS.equals(response.getStr("code")))
  82 + .setSerialNo(sendResult.getStr("smsMsgId")).setApiCode(sendResult.getStr("status"));
  83 + }
110 84  
111   - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
  85 + /**
  86 + * 请求华为云短信
  87 + *
  88 + * @see <a href="认证鉴权">https://support.huaweicloud.com/api-msgsms/sms_05_0046.html</a>
  89 + * @param uri 请求 URI
  90 + * @param method 请求 Method
  91 + * @param requestBody 请求 Body
  92 + * @return 请求结果
  93 + */
  94 + private JSONObject request(String uri, String method, String requestBody) {
  95 + // 1.1 请求 Header
112 96 TreeMap<String, String> headers = new TreeMap<>();
113 97 headers.put("Content-Type", "application/x-www-form-urlencoded");
  98 + String sdkDate = FastDateFormat.getInstance("yyyyMMdd'T'HHmmss'Z'", TimeZone.getTimeZone("UTC")).format(new Date());
114 99 headers.put("X-Sdk-Date", sdkDate);
115 100 headers.put("host", HOST);
116   - headers.put("Authorization", authorization);
117 101  
118   - String responseBody = HttpUtils.post(URL, headers, body);
  102 + // 1.2 构建签名 Header
  103 + String canonicalQueryString = ""; // 查询参数为空
  104 + String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
  105 + + "host:"+ HOST +"\n" + "x-sdk-date:" + sdkDate + "\n";
  106 + String canonicalRequest = method + "\n" + uri + "\n" + canonicalQueryString + "\n"
  107 + + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + sha256Hex(requestBody);
  108 + String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + sha256Hex(canonicalRequest);
  109 + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
  110 + headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey()
  111 + + ", " + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature);
  112 +
  113 + // 2. 发起请求
  114 + String responseBody = HttpUtils.post(URL, headers, requestBody);
119 115 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());
131   - }
132   -
133   - static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
134   - String statusCallBack, Long sendLogId) throws UnsupportedEncodingException {
135   - if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
136   - || templateId.isEmpty()) {
137   - System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
138   - return null;
139   - }
140   -
141   - StringBuilder body = new StringBuilder();
142   - appendToBody(body, "from=", sender);
143   - appendToBody(body, "&to=", receiver);
144   - appendToBody(body, "&templateId=", templateId);
145   - appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
146   - appendToBody(body, "&statusCallback=", statusCallBack);
147   - appendToBody(body, "&signature=", null);
148   - appendToBody(body, "&extend=", String.valueOf(sendLogId));
149   -
150   - return body.toString();
151 116 }
152 117  
153   - private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
154   - if (null != val && !val.isEmpty()) {
155   - body.append(key).append(URLEncoder.encode(val, "UTF-8"));
156   - }
157   - }
158 118 @Override
159 119 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;
  120 + Map<String, String> params = HttpUtil.decodeParamMap(requestBody, StandardCharsets.UTF_8);
  121 + // 字段参考 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html
  122 + return ListUtil.of(new SmsReceiveRespDTO()
  123 + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功
  124 + .setErrorCode(params.get("status")) // 状态报告编码
  125 + .setErrorMsg(params.get("statusDesc"))
  126 + .setMobile(params.get("to")) // 手机号
  127 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间
  128 + .setSerialNo(params.get("smsMsgId")) // 发送序列号
  129 + .setLogId(Long.valueOf(params.get("extend")))); // 用户序列号
188 130 }
189 131  
190 132 @Override
191 133 public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
192   - //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。
193   - return new SmsTemplateRespDTO().setId(null).setContent(null)
  134 + // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构,
  135 + // 所以将 sender 通道号,拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"(空格为分隔符)
  136 + String[] strs = apiTemplateId.split(" ");
  137 + Assert.isTrue(strs.length == 2, "格式不正确,需要满足:apiTemplateId sender");
  138 + return new SmsTemplateRespDTO().setId(strs[0]).setContent(null)
194 139 .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
195 140 }
  141 +
  142 + @SuppressWarnings("CharsetObjectCanBeUsed")
  143 + private static void appendToBody(StringBuilder body, String key, String value) throws UnsupportedEncodingException {
  144 + if (StrUtil.isNotEmpty(value)) {
  145 + body.append(key).append(URLEncoder.encode(value, CharsetUtil.CHARSET_UTF_8.name()));
  146 + }
  147 + }
  148 +
196 149 }
197 150 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
... ... @@ -56,10 +56,6 @@ public class TencentSmsClient extends AbstractSmsClient {
56 56 validateSdkAppId(properties);
57 57 }
58 58  
59   - @Override
60   - protected void doInit() {
61   - }
62   -
63 59 /**
64 60 * 参数校验腾讯云的 SDK AppId
65 61 *
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java
... ... @@ -37,14 +37,7 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
37 37 private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties);
38 38  
39 39 @Test
40   - public void testDoInit() {
41   - // 调用
42   - smsClient.doInit();
43   - }
44   -
45   - @Test
46 40 public void testDoSendSms_success() throws Throwable {
47   -
48 41 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
49 42 // 准备参数
50 43 Long sendLogId = randomLongId();
... ... @@ -55,9 +48,7 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
55 48  
56 49 // mock 方法
57 50 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   - );
  51 + .thenReturn("{\"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");
61 52  
62 53 // 调用
63 54 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
... ... @@ -66,12 +57,35 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
66 57 assertTrue(result.getSuccess());
67 58 assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
68 59 assertEquals("000000", result.getApiCode());
  60 + }
  61 + }
69 62  
  63 + @Test
  64 + public void testDoSendSms_fail_01() throws Throwable {
  65 + try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
  66 + // 准备参数
  67 + Long sendLogId = randomLongId();
  68 + String mobile = randomString();
  69 + String apiTemplateId = randomString() + " " + randomString();
  70 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  71 + new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
  72 +
  73 + // mock 方法
  74 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
  75 + .thenReturn("{\"result\":[{\"total\":1,\"originTo\":\"17321315478\",\"createTime\":\"2024-08-18T11:32:20Z\",\"from\":\"x8824060312575\",\"smsMsgId\":\"06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461\",\"countryId\":\"CN\",\"status\":\"E200033\"}],\"code\":\"E000510\",\"description\":\"The SMS fails to be sent. For details, see status.\"}");
  76 +
  77 + // 调用
  78 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
  79 + apiTemplateId, templateParams);
  80 + // 断言
  81 + assertFalse(result.getSuccess());
  82 + assertEquals("06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461", result.getSerialNo());
  83 + assertEquals("E200033", result.getApiCode());
70 84 }
71 85 }
72 86  
73 87 @Test
74   - public void testDoSendSms_fail() throws Throwable {
  88 + public void testDoSendSms_fail_02() throws Throwable {
75 89 try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
76 90 // 准备参数
77 91 Long sendLogId = randomLongId();
... ... @@ -82,17 +96,15 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
82 96  
83 97 // mock 方法
84 98 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   - );
  99 + .thenReturn("{\"code\":\"E000102\",\"description\":\"Invalid app_key.\"}");
88 100  
89 101 // 调用
90 102 SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
91 103 apiTemplateId, templateParams);
92 104 // 断言
93 105 assertFalse(result.getSuccess());
94   - assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
95   - assertEquals("E200015", result.getApiCode());
  106 + assertEquals("E000102", result.getApiCode());
  107 + assertEquals("Invalid app_key.", result.getApiMsg());
96 108 }
97 109 }
98 110  
... ... @@ -105,10 +117,11 @@ public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
105 117 List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
106 118 // 断言
107 119 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());
  120 + SmsReceiveRespDTO status = statuses.get(0);
  121 + assertTrue(status.getSuccess());
  122 + assertEquals("DELIVRD", status.getErrorCode());
  123 + assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), status.getReceiveTime());
  124 + assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", status.getSerialNo());
112 125 }
113 126  
114 127 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
1 1 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4   -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
  4 +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
5 5 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
6 6 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
7 7 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
... ... @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test;
11 11 import java.util.List;
12 12  
13 13 /**
14   - * 各种 {@link SmsClientTests 集成测试
  14 + * 各种 {@link SmsClient} 的集成测试
15 15 *
16 16 * @author 芋道源码
17 17 */
... ... @@ -23,8 +23,8 @@ public class SmsClientTests {
23 23 @Disabled
24 24 public void testAliyunSmsClient_getSmsTemplate() throws Throwable {
25 25 SmsChannelProperties properties = new SmsChannelProperties()
26   - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
27   - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz");
  26 + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY"))
  27 + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY"));
28 28 AliyunSmsClient client = new AliyunSmsClient(properties);
29 29 // 准备参数
30 30 String apiTemplateId = "SMS_207945135";
... ... @@ -38,9 +38,9 @@ public class SmsClientTests {
38 38 @Disabled
39 39 public void testAliyunSmsClient_sendSms() throws Throwable {
40 40 SmsChannelProperties properties = new SmsChannelProperties()
41   - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
42   - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
43   - .setSignature("runpu");
  41 + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY"))
  42 + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY"))
  43 + .setSignature("Ballcat");
44 44 AliyunSmsClient client = new AliyunSmsClient(properties);
45 45 // 准备参数
46 46 Long sendLogId = System.currentTimeMillis();
... ... @@ -52,35 +52,6 @@ public class SmsClientTests {
52 52 System.out.println(sendRespDTO);
53 53 }
54 54  
55   - @Test
56   - @Disabled
57   - public void testAliyunSmsClient_parseSmsReceiveStatus() {
58   - SmsChannelProperties properties = new SmsChannelProperties()
59   - .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
60   - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz");
61   - AliyunSmsClient client = new AliyunSmsClient(properties);
62   - // 准备参数
63   - String text = "[\n" +
64   - " {\n" +
65   - " \"phone_number\" : \"13900000001\",\n" +
66   - " \"send_time\" : \"2017-01-01 11:12:13\",\n" +
67   - " \"report_time\" : \"2017-02-02 22:23:24\",\n" +
68   - " \"success\" : true,\n" +
69   - " \"err_code\" : \"DELIVERED\",\n" +
70   - " \"err_msg\" : \"用户接收成功\",\n" +
71   - " \"sms_size\" : \"1\",\n" +
72   - " \"biz_id\" : \"12345\",\n" +
73   - " \"out_id\" : \"67890\"\n" +
74   - " }\n" +
75   - "]";
76   - // mock 方法
77   -
78   - // 调用
79   - List<SmsReceiveRespDTO> statuses = client.parseSmsReceiveStatus(text);
80   - // 打印结果
81   - System.out.println(statuses);
82   - }
83   -
84 55 // ========== 腾讯云 ==========
85 56  
86 57 @Test
... ... @@ -123,14 +94,14 @@ public class SmsClientTests {
123 94 @Disabled
124 95 public void testHuaweiSmsClient_sendSms() throws Throwable {
125 96 SmsChannelProperties properties = new SmsChannelProperties()
126   - .setApiKey("123")
127   - .setApiSecret("456")
  97 + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY"))
  98 + .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY"))
128 99 .setSignature("runpu");
129 100 HuaweiSmsClient client = new HuaweiSmsClient(properties);
130 101 // 准备参数
131 102 Long sendLogId = System.currentTimeMillis();
132   - String mobile = "15601691323";
133   - String apiTemplateId = "xx test01";
  103 + String mobile = "17321315478";
  104 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef x8824060312575";
134 105 List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024"));
135 106 // 调用
136 107 SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
... ...