mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-25 08:41:52 +08:00
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
This commit is contained in:
commit
8cce737523
@ -4,16 +4,17 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
|
||||
import com.xingyuv.captcha.util.StreamUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 短信回调")
|
||||
@ -26,7 +27,7 @@ public class SmsCallbackController {
|
||||
|
||||
@PostMapping("/aliyun")
|
||||
@PermitAll
|
||||
@Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/zh/sms/developer-reference/configure-delivery-receipts-1 文档")
|
||||
@Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
|
||||
public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
|
||||
String text = ServletUtils.getBody(request);
|
||||
smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
|
||||
@ -35,7 +36,7 @@ public class SmsCallbackController {
|
||||
|
||||
@PostMapping("/tencent")
|
||||
@PermitAll
|
||||
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/59178 文档")
|
||||
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
|
||||
public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
|
||||
String text = ServletUtils.getBody(request);
|
||||
smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
|
||||
@ -46,9 +47,8 @@ public class SmsCallbackController {
|
||||
@PostMapping("/huawei")
|
||||
@PermitAll
|
||||
@Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档")
|
||||
public CommonResult<Boolean> receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable {
|
||||
String text = ServletUtils.getBody(request);
|
||||
smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text);
|
||||
public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable {
|
||||
smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,11 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
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
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
// todo @scholar:参考阿里云在优化下
|
||||
/**
|
||||
@ -45,9 +41,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
|
||||
@Slf4j
|
||||
public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
|
||||
/**
|
||||
* 调用成功 code
|
||||
*/
|
||||
public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI
|
||||
public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
|
||||
public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
|
||||
@ -76,13 +69,14 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
|
||||
List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
|
||||
|
||||
JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack);
|
||||
SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
|
||||
JSONObject JsonResponse = request(sendLogId,sender,mobile,templateId,templateParas,statusCallBack);
|
||||
|
||||
return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
|
||||
return new SmsSendRespDTO().setSuccess("000000".equals(JsonResponse.getStr("code")))
|
||||
.setSerialNo(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("smsMsgId"))
|
||||
.setApiCode(JsonResponse.getJSONArray("result").getJSONObject(0).getStr("status"));
|
||||
}
|
||||
|
||||
JSONObject sendSmsRequest(String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
|
||||
JSONObject request(Long sendLogId,String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
@ -95,8 +89,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
|
||||
+ "host:"+ HOST +"\n"
|
||||
+ "x-sdk-date:" + sdkDate + "\n";
|
||||
//请求Body,不携带签名名称时,signature请填null
|
||||
String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null);
|
||||
String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, sendLogId);
|
||||
if (null == body || body.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@ -116,26 +109,29 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
+ "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature;
|
||||
|
||||
// ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
|
||||
HttpResponse response = HttpRequest.post(URL)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("X-Sdk-Date", sdkDate)
|
||||
.header("host",HOST)
|
||||
.header("Authorization", authorization)
|
||||
.body(body)
|
||||
.execute();
|
||||
TreeMap<String, String> headers = new TreeMap<>();
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
||||
headers.put("X-Sdk-Date", sdkDate);
|
||||
headers.put("host", HOST);
|
||||
headers.put("Authorization", authorization);
|
||||
|
||||
return JSONUtil.parseObj(response.body());
|
||||
}
|
||||
|
||||
private SmsResponse getSmsSendResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("000000".equals(resJson.getStr("code")));
|
||||
smsResponse.setData(resJson);
|
||||
return smsResponse;
|
||||
String responseBody = HttpUtils.post(URL, headers, body);
|
||||
return JSONUtil.parseObj(responseBody);
|
||||
//
|
||||
//
|
||||
// HttpResponse response = HttpRequest.post(URL)
|
||||
// .header("Content-Type", "application/x-www-form-urlencoded")
|
||||
// .header("X-Sdk-Date", sdkDate)
|
||||
// .header("host",HOST)
|
||||
// .header("Authorization", authorization)
|
||||
// .body(body)
|
||||
// .execute();
|
||||
//
|
||||
// return JSONUtil.parseObj(response.body());
|
||||
}
|
||||
|
||||
static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
|
||||
String statusCallBack, String signature) throws UnsupportedEncodingException {
|
||||
String statusCallBack, Long sendLogId) throws UnsupportedEncodingException {
|
||||
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|
||||
|| templateId.isEmpty()) {
|
||||
System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
|
||||
@ -148,7 +144,9 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
appendToBody(body, "&templateId=", templateId);
|
||||
appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
|
||||
appendToBody(body, "&statusCallback=", statusCallBack);
|
||||
appendToBody(body, "&signature=", signature);
|
||||
appendToBody(body, "&signature=", null);
|
||||
appendToBody(body, "&extend=", String.valueOf(sendLogId));
|
||||
|
||||
return body.toString();
|
||||
}
|
||||
|
||||
@ -158,12 +156,35 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(),"DELIVRD"))
|
||||
.setErrorCode(status.getStatus()).setErrorMsg(status.getStatus())
|
||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getUpdateTime())
|
||||
.setSerialNo(status.getSmsMsgId()));
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String requestBody) {
|
||||
|
||||
System.out.println("text in parseSmsReceiveStatus===== " + requestBody);
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
try {
|
||||
String[] pairs = requestBody.split("&");
|
||||
for (String pair : pairs) {
|
||||
int idx = pair.indexOf("=");
|
||||
String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8");
|
||||
String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8");
|
||||
params.put(key, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
List<SmsReceiveRespDTO> respDTOS = new ArrayList<>();
|
||||
respDTOS.add(new SmsReceiveRespDTO()
|
||||
.setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功
|
||||
.setErrorCode(params.get("status")) // 状态报告编码
|
||||
.setErrorMsg(params.get("statusDesc"))
|
||||
.setMobile(params.get("to")) // 手机号
|
||||
.setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间
|
||||
.setSerialNo(params.get("smsMsgId")) // 发送序列号
|
||||
.setLogId(Long.valueOf(params.get("extend")))//logId
|
||||
);
|
||||
|
||||
return respDTOS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -171,56 +192,5 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||
//华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。
|
||||
return new SmsTemplateRespDTO().setId(null).setContent(null)
|
||||
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SmsResponse {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 厂商原返回体
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 短信接收状态
|
||||
*
|
||||
* 参见 <a href="https://support.huaweicloud.com/api-msgsms/sms_05_0003.html">文档</a>
|
||||
*
|
||||
* @author scholar
|
||||
*/
|
||||
@Data
|
||||
public static class SmsReceiveStatus {
|
||||
|
||||
/**
|
||||
* 本条状态报告对应的短信的接收方号码,仅当状态报告中携带了extend参数时才会同时携带该参数
|
||||
*/
|
||||
@JsonProperty("to")
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 短信资源的更新时间,通常为短信平台接收短信状态报告的时间
|
||||
*/
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 短信状态报告枚举值
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 发送短信成功时返回的短信唯一标识。
|
||||
*/
|
||||
private String smsMsgId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
/**
|
||||
* {@link HuaweiSmsClient} 的单元测试
|
||||
*
|
||||
* @author scholar
|
||||
*/
|
||||
public class HuaweiSmsClientTest extends BaseMockitoUnitTest {
|
||||
|
||||
private final SmsChannelProperties properties = new SmsChannelProperties()
|
||||
.setApiKey(randomString())// 随机一个 apiKey,避免构建报错
|
||||
.setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
|
||||
.setSignature("芋道源码");
|
||||
|
||||
@InjectMocks
|
||||
private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties);
|
||||
|
||||
@Test
|
||||
public void testDoInit() {
|
||||
// 调用
|
||||
smsClient.doInit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms_success() throws Throwable {
|
||||
|
||||
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString() + " " + randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
|
||||
// mock 方法
|
||||
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
|
||||
.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"
|
||||
);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertTrue(result.getSuccess());
|
||||
assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
|
||||
assertEquals("000000", result.getApiCode());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms_fail() throws Throwable {
|
||||
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString() + " " + randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
|
||||
// mock 方法
|
||||
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
|
||||
.thenReturn(
|
||||
"{\"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"
|
||||
);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertFalse(result.getSuccess());
|
||||
assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo());
|
||||
assertEquals("E200015", result.getApiCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSmsReceiveStatus() {
|
||||
// 准备参数
|
||||
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";
|
||||
|
||||
// 调用
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
|
||||
// 断言
|
||||
assertEquals(1, statuses.size());
|
||||
assertTrue(statuses.getFirst().getSuccess());
|
||||
assertEquals("DELIVRD", statuses.getFirst().getErrorCode());
|
||||
assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), statuses.getFirst().getReceiveTime());
|
||||
assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", statuses.getFirst().getSerialNo());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user