From f81d56eb8836297076c790b264bc2d0a7191125a Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Thu, 18 Jul 2024 21:13:36 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=EF=BC=8C=E5=87=8F=E5=B0=91=E4=B8=BASDK=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=EF=BC=8C=E4=BF=AE=E6=94=B9=E4=B8=BA=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?API=E6=96=B9=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/AliyunSmsClient.java | 265 ++++++++++++++---- 1 file changed, 216 insertions(+), 49 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 7d01e6cdf..708683b21 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -1,6 +1,14 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; +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.MapUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -9,27 +17,23 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.aliyuncs.DefaultAcsClient; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; -import com.aliyuncs.profile.DefaultProfile; -import com.aliyuncs.profile.IClientProfile; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLEncoder; import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; +import java.util.*; 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; +import java.text.SimpleDateFormat; /** * 阿里短信客户端的实现类 @@ -40,21 +44,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE @Slf4j public class AliyunSmsClient extends AbstractSmsClient { - /** - * 调用成功 code - */ - public static final String API_CODE_SUCCESS = "OK"; - - /** - * REGION, 使用杭州 - */ - private static final String ENDPOINT = "cn-hangzhou"; - - /** - * 阿里云客户端 - */ - private volatile IAcsClient client; - public AliyunSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); @@ -63,24 +52,116 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override protected void doInit() { - IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); - client = new DefaultAcsClient(profile); +// IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); +// client = new DefaultAcsClient(profile); } @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 构建请求 - SendSmsRequest request = new SendSmsRequest(); - request.setPhoneNumbers(mobile); - request.setSignName(properties.getSignature()); - request.setTemplateCode(apiTemplateId); - request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); - request.setOutId(String.valueOf(sendLogId)); - // 执行请求 - SendSmsResponse response = client.getAcsResponse(request); - return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId()) - .setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage()); + + TreeMap queryParam = new TreeMap<>(); + queryParam.put("PhoneNumbers",mobile); + queryParam.put("SignName",properties.getSignature()); + queryParam.put("TemplateCode",apiTemplateId); + queryParam.put("TemplateParam",JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + + JSONObject response = sendSmsRequest(queryParam,"sendSms"); + SmsResponse smsResponse = getSmsSendResponse(response); + + return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + } + + JSONObject sendSmsRequest(TreeMap queryParam,String apiName) throws IOException, URISyntaxException { + + // ************* 步骤 1:拼接规范请求串 ************* + String url = "https://dysmsapi.aliyuncs.com"; //APP接入地址+接口访问URI + String httpMethod = "POST"; // 请求方式 + String canonicalUri = "/"; + // 请求参数,当请求的查询字符串为空时,使用空字符串作为规范化查询字符串 + StringBuilder canonicalQueryString = new StringBuilder(); + queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { + // 如果canonicalQueryString已经不是空的,则在查询参数前添加"&" + if (!canonicalQueryString.isEmpty()) { + canonicalQueryString.append("&"); + } + canonicalQueryString.append(queryPart); + System.out.println("canonicalQueryString=========>\n" + canonicalQueryString); + }); + + SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); + String SdfTime = SDF.format(new Date()); + String randomUUID = UUID.randomUUID().toString(); + + TreeMap headers = new TreeMap<>(); + headers.put("host", "dysmsapi.aliyuncs.com"); + headers.put("x-acs-action", apiName); + headers.put("x-acs-version", "2017-05-25"); + headers.put("x-acs-date", SdfTime); + headers.put("x-acs-signature-nonce", randomUUID); +// headers.put("content-type", "application/json;charset=utf-8"); + + // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder canonicalHeaders = new StringBuilder(); + // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + StringBuilder signedHeadersSb = new StringBuilder(); + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + String value = String.valueOf(entry.getValue()).trim(); + canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); + signedHeadersSb.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); + + String body = "";//短信API为RPC接口,query parameters在uri中拼接,因此request body如果没有特殊要求,设置为空。 + String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); + + + String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + System.out.println("canonicalRequest=========>\n" + canonicalRequest); + + // ************* 步骤 2:拼接待签名字符串 ************* + String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + + // ************* 步骤 3:计算签名 ************* + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); + + // ************* 步骤 4:拼接 Authorization ************* + String authorization = "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + headers.put("Authorization", authorization); + + // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* +// url = url + canonicalUri; + String urlWithParams = url + "?" + URLUtil.buildQuery(queryParam, null); + + HttpResponse response = HttpRequest.post(urlWithParams) + .addHeaders(headers) + .body(body) + .execute(); +// URIBuilder uriBuilder = new URIBuilder(url); +// // 添加请求参数 +// for (Map.Entry entry : queryParam.entrySet()) { +// uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); +// } +// HttpUriRequest httpRequest = new HttpPost(uriBuilder.build()); +//// HttpPost httpPost = new HttpPost(uriBuilder.build()); +//// httpRequest = httpPost; +// +// // 添加http请求头 +// for (Map.Entry entry : headers.entrySet()) { +// httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); +// } +// +// // 发送请求 +// CloseableHttpClient httpClient = HttpClients.createDefault(); +// CloseableHttpResponse response = httpClient.execute(httpRequest); + System.out.println("getEntity====="+response.body()); + System.out.println("response====="+response); + + return JSONUtil.parseObj(response.body()); } @Override @@ -94,16 +175,15 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - // 构建请求 - QuerySmsTemplateRequest request = new QuerySmsTemplateRequest(); - request.setTemplateCode(apiTemplateId); - // 执行请求 - QuerySmsTemplateResponse response = client.getAcsResponse(request); - if (response.getTemplateStatus() == null) { - return null; - } - return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason()); + + TreeMap queryParam = new TreeMap<>(); + queryParam.put("TemplateCode",apiTemplateId); + + JSONObject response = sendSmsRequest(queryParam,"QuerySmsTemplate"); + QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(response); + return new SmsTemplateRespDTO().setId(smsTemplateResponse.getTemplateCode()).setContent(smsTemplateResponse.getTemplateContent()) + .setAuditStatus(convertSmsTemplateAuditStatus(smsTemplateResponse.getTemplateStatus())).setAuditReason(smsTemplateResponse.getReason()); + } @VisibleForTesting @@ -116,12 +196,99 @@ public class AliyunSmsClient extends AbstractSmsClient { } } + + /** + * 对指定的字符串进行URL编码。 + * 使用UTF-8编码字符集对字符串进行编码,并对特定的字符进行替换,以符合URL编码规范。 + * + * @param str 需要进行URL编码的字符串。 + * @return 编码后的字符串。其中,加号"+"被替换为"%20",星号"*"被替换为"%2A",波浪号"%7E"被替换为"~"。 + */ + public static String percentCode(String str) { + if (str == null) { + throw new IllegalArgumentException("输入字符串不可为null"); + } + try { + return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8编码不被支持", e); + } + } + + private SmsResponse getSmsSendResponse(JSONObject resJson) { + SmsResponse smsResponse = new SmsResponse(); + smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); + smsResponse.setData(resJson); +// smsResponse.setConfigId(getConfigId()); + return smsResponse; + } + + private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { + + QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); + + smsTemplateResponse.setRequestId(resJson.getStr("RequestId")); + smsTemplateResponse.setTemplateContent(resJson.getStr("TemplateContent")); + smsTemplateResponse.setReason(resJson.getStr("Reason")); + smsTemplateResponse.setTemplateStatus(resJson.getInt("TemplateStatus")); + + return smsTemplateResponse; + } + + /** + *

类名: SmsResponse + *

说明: 发送短信返回信息 + * + * @author :scholar + * 2024/07/17 0:25 + **/ + @Data + public static class SmsResponse { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 厂商原返回体 + */ + private Object data; + + /** + * 配置标识名 如未配置取对应渠道名例如 Alibaba + */ + private String configId; + } + + + /** + *

类名: QuerySmsTemplateResponse + *

说明: sms模板查询返回信息 + * + * @author :scholar + * 2024/07/17 0:25 + **/ + @Data + public static class QuerySmsTemplateResponse { + private String requestId; + private String code; + private String message; + private Integer templateStatus; + private String reason; + private String templateCode; + private Integer templateType; + private String templateName; + private String templateContent; + private String createDate; + } + /** * 短信接收状态 * * 参见 文档 * - * @author 芋道源码 + * @author 润普源码 */ @Data public static class SmsReceiveStatus { From 22ff197f02fde92c7cceead31a1f06809282f2d9 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Fri, 26 Jul 2024 22:06:10 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E5=AE=8C=E6=88=90todo=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=9A=84=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/HuaweiSmsClient.java | 117 ++++++++++-------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index 84bc2645e..4df820861 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -2,35 +2,29 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.json.JSONArray; +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.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; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import org.apache.http.client.methods.*; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpResponse; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; @@ -38,6 +32,7 @@ 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; @@ -55,88 +50,90 @@ public class HuaweiSmsClient extends AbstractSmsClient { /** * 调用成功 code */ - public static final String API_CODE_SUCCESS = "OK"; - private static final Logger LOGGER = LoggerFactory.getLogger(HuaweiSmsClient.class); + 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"; + + @Override + protected void doInit() { + + } public HuaweiSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override - protected void doInit() { - } @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID - //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔 - String receiver = mobile; //短信接收人号码 - //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 String statusCallBack = properties.getCallbackUrl(); + List templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue())); + + JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack); + SmsResponse smsResponse = getSmsSendResponse(JsonResponse); + + return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + } + + JSONObject sendSmsRequest(String sender,String mobile,String templateId,List templateParas,String statusCallBack) throws UnsupportedEncodingException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - String singerDate = sdf.format(new Date()); + String sdkDate = sdf.format(new Date()); // ************* 步骤 1:拼接规范请求串 ************* String httpRequestMethod = "POST"; String canonicalUri = "/sms/batchSendSms/v1/"; String canonicalQueryString = "";//查询参数为空 String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" - + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n" - + "x-sdk-date:" + singerDate + "\n"; - String signedHeaders = "content-type;host;x-sdk-date"; - /** - * 选填,使用无变量模板时请赋空值 String templateParas = ""; - * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" - * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]" - */ - List templateParas = new ArrayList<>(); - for (KeyValue kv : templateParams) { - templateParas.add(String.valueOf(kv.getValue())); - } - + + "host:"+ HOST +"\n" + + "x-sdk-date:" + sdkDate + "\n"; //请求Body,不携带签名名称时,signature请填null - String body = buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, null); + String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null); if (null == body || body.isEmpty()) { return null; } - String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); + String hashedRequestBody = sha256Hex(body); String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" - + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + hashedRequestBody; // ************* 步骤 2:拼接待签名字符串 ************* - String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); - String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest; + String hashedCanonicalRequest = sha256Hex(canonicalRequest); + String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + hashedCanonicalRequest; // ************* 步骤 3:计算签名 ************* String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // ************* 步骤 4:拼接 Authorization ************* String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", " - + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature; // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* - HttpUriRequest postMethod = RequestBuilder.post() - .setUri(url) - .setEntity(new StringEntity(body, StandardCharsets.UTF_8)) - .setHeader("Content-Type","application/x-www-form-urlencoded") - .setHeader("X-Sdk-Date",singerDate) - .setHeader("Authorization",authorization) - .build(); - CloseableHttpClient client = HttpClientBuilder.create().build(); - HttpResponse response = client.execute(postMethod); + 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 new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) - .setApiRequestId(null).setApiCode(null).setApiMsg(null); + 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; } static String buildRequestBody(String sender, String receiver, String templateId, List templateParas, @@ -151,7 +148,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { appendToBody(body, "from=", sender); appendToBody(body, "&to=", receiver); appendToBody(body, "&templateId=", templateId); - appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString()); + appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas)); appendToBody(body, "&statusCallback=", statusCallBack); appendToBody(body, "&signature=", signature); return body.toString(); @@ -179,6 +176,22 @@ public class HuaweiSmsClient extends AbstractSmsClient { } + @Data + public static class SmsResponse { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 厂商原返回体 + */ + private Object data; + + } + + /** * 短信接收状态 * From 08e1c69d10a5c946ab9d32d7d605fe7ffc93c96f Mon Sep 17 00:00:00 2001 From: zhougang <921366807@qq.com> Date: Wed, 31 Jul 2024 22:43:48 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=84=B1=E6=95=8F=E6=94=AF=E6=8C=81=20Spring?= =?UTF-8?q?=20el=20=E8=A1=A8=E8=BE=BE=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=E8=84=B1?= =?UTF-8?q?=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/spring/SpringExpressionUtils.java | 21 +++++++++++++++++++ .../yudao-spring-boot-starter-web/pom.xml | 5 +++++ .../regex/annotation/EmailDesensitize.java | 6 ++++++ .../regex/annotation/RegexDesensitize.java | 6 ++++++ .../AbstractRegexDesensitizationHandler.java | 14 +++++++++++++ .../DefaultRegexDesensitizationHandler.java | 6 ++++++ .../handler/EmailDesensitizationHandler.java | 5 +++++ .../annotation/BankCardDesensitize.java | 5 +++++ .../annotation/CarLicenseDesensitize.java | 5 +++++ .../annotation/ChineseNameDesensitize.java | 5 +++++ .../annotation/FixedPhoneDesensitize.java | 5 +++++ .../slider/annotation/IdCardDesensitize.java | 5 +++++ .../slider/annotation/MobileDesensitize.java | 5 +++++ .../annotation/PasswordDesensitize.java | 5 +++++ .../slider/annotation/SliderDesensitize.java | 6 ++++++ .../AbstractSliderDesensitizationHandler.java | 14 +++++++++++++ .../handler/BankCardDesensitization.java | 5 +++++ .../handler/CarLicenseDesensitization.java | 6 ++++++ .../handler/ChineseNameDesensitization.java | 5 +++++ .../DefaultDesensitizationHandler.java | 6 ++++++ .../handler/FixedPhoneDesensitization.java | 6 ++++++ .../slider/handler/IdCardDesensitization.java | 6 ++++++ .../slider/handler/MobileDesensitization.java | 6 ++++++ .../handler/PasswordDesensitization.java | 6 ++++++ 24 files changed, 164 insertions(+) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java index 9a7f8812b..e9670d88d 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java @@ -3,11 +3,15 @@ package cn.iocoder.yudao.framework.common.util.spring; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -86,4 +90,21 @@ public class SpringExpressionUtils { return result; } + /** + * 从 Bean 工厂,解析 EL 表达式的结果 + * + * @param beanFactory Bean 工程 + * @param expressionString EL 表达式 + * @return 执行界面 + */ + public static Object parseExpression(BeanFactory beanFactory, String expressionString) { + if (StrUtil.isBlank(expressionString)) { + return null; + } + Expression expression = EXPRESSION_PARSER.parseExpression(expressionString); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + return expression.getValue(context); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/pom.xml b/yudao-framework/yudao-spring-boot-starter-web/pom.xml index b5d0aa84d..5861f198f 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-web/pom.xml @@ -32,6 +32,11 @@ spring-boot-configuration-processor true + + org.aspectj + aspectjweaver + provided + com.github.xiaoymin diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java index 227f25499..5485c194e 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java @@ -33,4 +33,10 @@ public @interface EmailDesensitize { * 比如:example@gmail.com 脱敏之后为 e****@gmail.com */ String replacer() default "$1****$2"; + + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java index 4ab7c7415..9b0a3fa38 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java @@ -35,4 +35,10 @@ public @interface RegexDesensitize { * 脱敏后字符串 ******456789 */ String replacer() default "******"; + + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java index f43431b1d..11d3dc23b 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.framework.desensitize.core.regex.handler; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; import java.lang.annotation.Annotation; @@ -14,6 +16,10 @@ public abstract class AbstractRegexDesensitizationHandler @Override public String desensitize(String origin, T annotation) { + Object expressionResult = SpringExpressionUtils.parseExpression(SpringUtil.getApplicationContext(), getCondition(annotation)); + if (expressionResult instanceof Boolean && (Boolean) expressionResult) { + return origin; + } String regex = getRegex(annotation); String replacer = getReplacer(annotation); return origin.replaceAll(regex, replacer); @@ -35,4 +41,12 @@ public abstract class AbstractRegexDesensitizationHandler */ abstract String getReplacer(T annotation); + /** + * el 表达式 + * + * @param annotation 注解信息 + * @return el 表达式 + */ + abstract String getCondition(T annotation); + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java index f92414e0c..75fa8895d 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java @@ -18,4 +18,10 @@ public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitiza String getReplacer(RegexDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(RegexDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java index 8d1867a64..fec46d449 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java @@ -19,4 +19,9 @@ public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHan return annotation.replacer(); } + @Override + String getCondition(EmailDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java index 19ad54e25..f512f4c9e 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java @@ -37,4 +37,9 @@ public @interface BankCardDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java index 9000e1ec4..93e895bf4 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java @@ -37,4 +37,9 @@ public @interface CarLicenseDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java index 73a0d0ee5..05c22dd77 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java @@ -37,4 +37,9 @@ public @interface ChineseNameDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java index 862235346..ade43793b 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java @@ -37,4 +37,9 @@ public @interface FixedPhoneDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java index 8a654c915..b3bbb45e8 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java @@ -37,4 +37,9 @@ public @interface IdCardDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java index f0c42f192..9a8c55708 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java @@ -37,4 +37,9 @@ public @interface MobileDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java index 6a3b2694f..f4c6bac98 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java @@ -39,4 +39,9 @@ public @interface PasswordDesensitize { */ String replacer() default "*"; + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java index ec79635b9..13a5becfa 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java @@ -40,4 +40,10 @@ public @interface SliderDesensitize { * 前缀保留长度 */ int prefixKeep() default 0; + + /** + * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + */ + String condition() default ""; + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java index 7dd2a7fd1..b3b076984 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.framework.desensitize.core.slider.handler; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; import java.lang.annotation.Annotation; @@ -14,6 +16,10 @@ public abstract class AbstractSliderDesensitizationHandler @Override public String desensitize(String origin, T annotation) { + Object expressionResult = SpringExpressionUtils.parseExpression(SpringUtil.getApplicationContext(), getCondition(annotation)); + if (expressionResult instanceof Boolean && (Boolean) expressionResult) { + return origin; + } int prefixKeep = getPrefixKeep(annotation); int suffixKeep = getSuffixKeep(annotation); String replacer = getReplacer(annotation); @@ -75,4 +81,12 @@ public abstract class AbstractSliderDesensitizationHandler */ abstract String getReplacer(T annotation); + /** + * el 表达式 + * + * @param annotation 注解信息 + * @return el 表达式 + */ + abstract String getCondition(T annotation); + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java index e1d90ea6d..c56edc78d 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java @@ -24,4 +24,9 @@ public class BankCardDesensitization extends AbstractSliderDesensitizationHandle return annotation.replacer(); } + @Override + String getCondition(BankCardDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java index 34b3e9a69..6829c0688 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java @@ -22,4 +22,10 @@ public class CarLicenseDesensitization extends AbstractSliderDesensitizationHand String getReplacer(CarLicenseDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(CarLicenseDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java index f71dac0e0..e9be53176 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java @@ -24,4 +24,9 @@ public class ChineseNameDesensitization extends AbstractSliderDesensitizationHan return annotation.replacer(); } + @Override + String getCondition(ChineseNameDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java index 8b0adaeab..4fdadf3b7 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java @@ -22,4 +22,10 @@ public class DefaultDesensitizationHandler extends AbstractSliderDesensitization String getReplacer(SliderDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(SliderDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java index 6e2326171..6a566f4df 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java @@ -22,4 +22,10 @@ public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHand String getReplacer(FixedPhoneDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(FixedPhoneDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java index 9d525b34c..b85c9a16c 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java @@ -22,4 +22,10 @@ public class IdCardDesensitization extends AbstractSliderDesensitizationHandler< String getReplacer(IdCardDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(IdCardDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java index 582900ad4..102d07e35 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java @@ -23,4 +23,10 @@ public class MobileDesensitization extends AbstractSliderDesensitizationHandler< String getReplacer(MobileDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(MobileDesensitize annotation) { + return annotation.condition(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java index 1bccaa2a4..1ebaca2e9 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java @@ -22,4 +22,10 @@ public class PasswordDesensitization extends AbstractSliderDesensitizationHandle String getReplacer(PasswordDesensitize annotation) { return annotation.replacer(); } + + @Override + String getCondition(PasswordDesensitize annotation) { + return annotation.condition(); + } + } From 7ba3b123131bdd792bbca9e46e7a7497895e101e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 3 Aug 2024 18:48:06 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=84=B1=E6=95=8F=E6=94=AF=E6=8C=81=20Spring?= =?UTF-8?q?=20el=20=E8=A1=A8=E8=BE=BE=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=E8=84=B1?= =?UTF-8?q?=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/spring/SpringExpressionUtils.java | 7 +++---- .../base/handler/DesensitizationHandler.java | 19 +++++++++++++++++++ .../regex/annotation/EmailDesensitize.java | 6 ++++-- .../regex/annotation/RegexDesensitize.java | 6 ++++-- .../AbstractRegexDesensitizationHandler.java | 16 +++++----------- .../DefaultRegexDesensitizationHandler.java | 4 ++-- .../handler/EmailDesensitizationHandler.java | 5 ----- .../annotation/BankCardDesensitize.java | 6 ++++-- .../annotation/CarLicenseDesensitize.java | 6 ++++-- .../annotation/ChineseNameDesensitize.java | 6 ++++-- .../annotation/FixedPhoneDesensitize.java | 6 ++++-- .../slider/annotation/IdCardDesensitize.java | 6 ++++-- .../slider/annotation/MobileDesensitize.java | 6 ++++-- .../annotation/PasswordDesensitize.java | 6 ++++-- .../slider/annotation/SliderDesensitize.java | 6 ++++-- .../AbstractSliderDesensitizationHandler.java | 16 +++++----------- .../handler/BankCardDesensitization.java | 4 ++-- .../handler/CarLicenseDesensitization.java | 5 +++-- .../handler/ChineseNameDesensitization.java | 5 ----- .../DefaultDesensitizationHandler.java | 6 +----- .../handler/FixedPhoneDesensitization.java | 6 +----- .../slider/handler/IdCardDesensitization.java | 5 ----- .../slider/handler/MobileDesensitization.java | 5 ----- .../handler/PasswordDesensitization.java | 5 ----- 24 files changed, 81 insertions(+), 87 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java index e9670d88d..069e89db3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java @@ -4,9 +4,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.BeanFactory; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; @@ -93,17 +93,16 @@ public class SpringExpressionUtils { /** * 从 Bean 工厂,解析 EL 表达式的结果 * - * @param beanFactory Bean 工程 * @param expressionString EL 表达式 * @return 执行界面 */ - public static Object parseExpression(BeanFactory beanFactory, String expressionString) { + public static Object parseExpression(String expressionString) { if (StrUtil.isBlank(expressionString)) { return null; } Expression expression = EXPRESSION_PARSER.parseExpression(expressionString); StandardEvaluationContext context = new StandardEvaluationContext(); - context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext())); return expression.getValue(context); } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java index 470a0becf..b15e35623 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.framework.desensitize.core.base.handler; +import cn.hutool.core.util.ReflectUtil; + import java.lang.annotation.Annotation; /** @@ -18,4 +20,21 @@ public interface DesensitizationHandler { */ String desensitize(String origin, T annotation); + /** + * 是否禁用脱敏的 Spring EL 表达式 + * + * 如果返回 true 则跳过脱敏 + * + * @param annotation 注解信息 + * @return 是否禁用脱敏的 Spring EL 表达式 + */ + default String getDisable(T annotation) { + // 约定:默认就是 enable() 属性。如果不符合,子类重写 + try { + return (String) ReflectUtil.invoke(annotation, "disable"); + } catch (Exception ex) { + return ""; + } + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java index 5485c194e..cb838de93 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java @@ -35,8 +35,10 @@ public @interface EmailDesensitize { String replacer() default "$1****$2"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java index 9b0a3fa38..49d002a26 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java @@ -37,8 +37,10 @@ public @interface RegexDesensitize { String replacer() default "******"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java index 11d3dc23b..6ae1a50ff 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.desensitize.core.regex.handler; -import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; @@ -16,10 +15,13 @@ public abstract class AbstractRegexDesensitizationHandler @Override public String desensitize(String origin, T annotation) { - Object expressionResult = SpringExpressionUtils.parseExpression(SpringUtil.getApplicationContext(), getCondition(annotation)); - if (expressionResult instanceof Boolean && (Boolean) expressionResult) { + // 1. 判断是否禁用脱敏 + Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation)); + if (Boolean.TRUE.equals(disable)) { return origin; } + + // 2. 执行脱敏 String regex = getRegex(annotation); String replacer = getReplacer(annotation); return origin.replaceAll(regex, replacer); @@ -41,12 +43,4 @@ public abstract class AbstractRegexDesensitizationHandler */ abstract String getReplacer(T annotation); - /** - * el 表达式 - * - * @param annotation 注解信息 - * @return el 表达式 - */ - abstract String getCondition(T annotation); - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java index 75fa8895d..debbe636f 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java @@ -20,8 +20,8 @@ public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitiza } @Override - String getCondition(RegexDesensitize annotation) { - return annotation.condition(); + public String getDisable(RegexDesensitize annotation) { + return annotation.disable(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java index fec46d449..8d1867a64 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java @@ -19,9 +19,4 @@ public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHan return annotation.replacer(); } - @Override - String getCondition(EmailDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java index f512f4c9e..7f08a4395 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java @@ -38,8 +38,10 @@ public @interface BankCardDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java index 93e895bf4..91dae8617 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java @@ -38,8 +38,10 @@ public @interface CarLicenseDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java index 05c22dd77..866ec22ec 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java @@ -38,8 +38,10 @@ public @interface ChineseNameDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java index ade43793b..23273b873 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java @@ -38,8 +38,10 @@ public @interface FixedPhoneDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java index b3bbb45e8..6d97e107a 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java @@ -38,8 +38,10 @@ public @interface IdCardDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java index 9a8c55708..c0f5211c6 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java @@ -38,8 +38,10 @@ public @interface MobileDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java index f4c6bac98..e37c473bd 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java @@ -40,8 +40,10 @@ public @interface PasswordDesensitize { String replacer() default "*"; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java index 13a5becfa..ee5d867ee 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java @@ -42,8 +42,10 @@ public @interface SliderDesensitize { int prefixKeep() default 0; /** - * el 表达式,当执行 condition 返回 true 的时候,跳过脱敏 + * 是否禁用脱敏 + * + * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏 */ - String condition() default ""; + String disable() default ""; } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java index b3b076984..66933f7cd 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.desensitize.core.slider.handler; -import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler; @@ -16,10 +15,13 @@ public abstract class AbstractSliderDesensitizationHandler @Override public String desensitize(String origin, T annotation) { - Object expressionResult = SpringExpressionUtils.parseExpression(SpringUtil.getApplicationContext(), getCondition(annotation)); - if (expressionResult instanceof Boolean && (Boolean) expressionResult) { + // 1. 判断是否禁用脱敏 + Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation)); + if (Boolean.FALSE.equals(disable)) { return origin; } + + // 2. 执行脱敏 int prefixKeep = getPrefixKeep(annotation); int suffixKeep = getSuffixKeep(annotation); String replacer = getReplacer(annotation); @@ -81,12 +83,4 @@ public abstract class AbstractSliderDesensitizationHandler */ abstract String getReplacer(T annotation); - /** - * el 表达式 - * - * @param annotation 注解信息 - * @return el 表达式 - */ - abstract String getCondition(T annotation); - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java index c56edc78d..79797e5fe 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java @@ -25,8 +25,8 @@ public class BankCardDesensitization extends AbstractSliderDesensitizationHandle } @Override - String getCondition(BankCardDesensitize annotation) { - return annotation.condition(); + public String getDisable(BankCardDesensitize annotation) { + return ""; } } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java index 6829c0688..1029ee259 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseD * @author gaibu */ public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler { + @Override Integer getPrefixKeep(CarLicenseDesensitize annotation) { return annotation.prefixKeep(); @@ -24,8 +25,8 @@ public class CarLicenseDesensitization extends AbstractSliderDesensitizationHand } @Override - String getCondition(CarLicenseDesensitize annotation) { - return annotation.condition(); + public String getDisable(CarLicenseDesensitize annotation) { + return annotation.disable(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java index e9be53176..f71dac0e0 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java @@ -24,9 +24,4 @@ public class ChineseNameDesensitization extends AbstractSliderDesensitizationHan return annotation.replacer(); } - @Override - String getCondition(ChineseNameDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java index 4fdadf3b7..bdb282dc0 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesen * @author gaibu */ public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler { + @Override Integer getPrefixKeep(SliderDesensitize annotation) { return annotation.prefixKeep(); @@ -23,9 +24,4 @@ public class DefaultDesensitizationHandler extends AbstractSliderDesensitization return annotation.replacer(); } - @Override - String getCondition(SliderDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java index 6a566f4df..53412e49a 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneD * @author gaibu */ public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler { + @Override Integer getPrefixKeep(FixedPhoneDesensitize annotation) { return annotation.prefixKeep(); @@ -23,9 +24,4 @@ public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHand return annotation.replacer(); } - @Override - String getCondition(FixedPhoneDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java index b85c9a16c..4bb89157d 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java @@ -23,9 +23,4 @@ public class IdCardDesensitization extends AbstractSliderDesensitizationHandler< return annotation.replacer(); } - @Override - String getCondition(IdCardDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java index 102d07e35..5796d13cd 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java @@ -24,9 +24,4 @@ public class MobileDesensitization extends AbstractSliderDesensitizationHandler< return annotation.replacer(); } - @Override - String getCondition(MobileDesensitize annotation) { - return annotation.condition(); - } - } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java index 1ebaca2e9..8c6d1204a 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java @@ -23,9 +23,4 @@ public class PasswordDesensitization extends AbstractSliderDesensitizationHandle return annotation.replacer(); } - @Override - String getCondition(PasswordDesensitize annotation) { - return annotation.condition(); - } - } From 2192129220388d4d877893e057bbd6f13ee935ef Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Wed, 7 Aug 2024 10:52:07 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E5=AE=8C=E6=88=90todo=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=9A=84=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/client/impl/TencentSmsClient.java | 212 +++++++++++++++--- 1 file changed, 175 insertions(+), 37 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index dc238be77..f18598b07 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -2,6 +2,11 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; +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.ArrayUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -13,15 +18,17 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; -import com.tencentcloudapi.common.Credential; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.*; import lombok.Data; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; +import java.util.*; +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; @@ -40,11 +47,6 @@ public class TencentSmsClient extends AbstractSmsClient { */ public static final String API_CODE_SUCCESS = "Ok"; - /** - * REGION,使用南京 - */ - private static final String ENDPOINT = "ap-nanjing"; - /** * 是否国际/港澳台短信: * @@ -53,7 +55,6 @@ public class TencentSmsClient extends AbstractSmsClient { */ private static final long INTERNATIONAL_CHINA = 0L; - private SmsClient client; public TencentSmsClient(SmsChannelProperties properties) { super(properties); @@ -63,9 +64,7 @@ public class TencentSmsClient extends AbstractSmsClient { @Override protected void doInit() { - // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey - Credential credential = new Credential(getApiKey(), properties.getApiSecret()); - client = new SmsClient(credential, ENDPOINT); + } /** @@ -96,18 +95,87 @@ public class TencentSmsClient extends AbstractSmsClient { public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { // 构建请求 - SendSmsRequest request = new SendSmsRequest(); - request.setSmsSdkAppId(getSdkAppId()); - request.setPhoneNumberSet(new String[]{mobile}); - request.setSignName(properties.getSignature()); - request.setTemplateId(apiTemplateId); - request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue()))); - request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId))); - // 执行请求 - SendSmsResponse response = client.SendSms(request); - SendStatus status = response.getSendStatusSet()[0]; - return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo()) - .setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage()); + TreeMap body = new TreeMap<>(); + String[] phones = {mobile}; + body.put("PhoneNumberSet",phones); + body.put("SmsSdkAppId",getSdkAppId()); + body.put("SignName",properties.getSignature()); + body.put("TemplateId",apiTemplateId); + body.put("TemplateParamSet",ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue()))); + + JSONObject JsonResponse = sendSmsRequest(body,"SendSms","2021-01-11","ap-guangzhou"); + SmsResponse smsResponse = getSmsSendResponse(JsonResponse); + + return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + + } + + JSONObject sendSmsRequest(TreeMap body,String action,String version,String region) throws Exception { + + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + // 注意时区,否则容易出错 + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String date = sdf.format(new Date(Long.valueOf(timestamp + "000"))); + + // ************* 步骤 1:拼接规范请求串 ************* + String host = "sms.tencentcloudapi.com"; //APP接入地址+接口访问URI + String httpMethod = "POST"; // 请求方式 + String canonicalUri = "/"; + String canonicalQueryString = ""; + + String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n"; + String signedHeaders = "content-type;host;x-tc-action"; + String hashedRequestBody = sha256Hex(JSONUtil.toJsonStr(body)); + String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + + // ************* 步骤 2:拼接待签名字符串 ************* + String credentialScope = date + "/" + "sms" + "/" + "tc3_request"; + String hashedCanonicalRequest = sha256Hex(canonicalRequest); + String stringToSign = "TC3-HMAC-SHA256" + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest; + + // ************* 步骤 3:计算签名 ************* + byte[] secretDate = hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), date); + byte[] secretService = hmac256(secretDate, "sms"); + byte[] secretSigning = hmac256(secretService, "tc3_request"); + String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase(); + + // ************* 步骤 4:拼接 Authorization ************* + String authorization = "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + + // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* + Map headers = new HashMap<>(); + headers.put("Authorization", authorization); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("Host", host); + headers.put("X-TC-Action", action); + headers.put("X-TC-Timestamp", timestamp); + headers.put("X-TC-Version", version); + headers.put("X-TC-Region", region); + + HttpResponse response = HttpRequest.post("https://"+host) + .addHeaders(headers) + .body(JSONUtil.toJsonStr(body)) + .execute(); + + return JSONUtil.parseObj(response.body()); + } + + public static byte[] hmac256(byte[] key, String msg) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); + mac.init(secretKeySpec); + return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8)); + } + + private SmsResponse getSmsSendResponse(JSONObject resJson) { + SmsResponse smsResponse = new SmsResponse(); + JSONArray statusJson =resJson.getJSONObject("Response").getJSONArray("SendStatusSet"); + smsResponse.setSuccess("Ok".equals(statusJson.getJSONObject(0).getStr("Code"))); + smsResponse.setData(resJson); + return smsResponse; } @Override @@ -122,18 +190,49 @@ public class TencentSmsClient extends AbstractSmsClient { @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 构建请求 - DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest(); - request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)}); - request.setInternational(INTERNATIONAL_CHINA); - // 执行请求 - DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request); - DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0]; - if (status == null || status.getStatusCode() == null) { - return null; - } - return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply()); + TreeMap body = new TreeMap<>(); + body.put("International",0); + Integer[] templateIds = {Integer.valueOf(apiTemplateId)}; + body.put("TemplateIdSet",templateIds); + + JSONObject JsonResponse = sendSmsRequest(body,"DescribeSmsTemplateList","2021-01-11","ap-guangzhou"); + QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(JsonResponse); + String templateId = Integer.toString(smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateId()); + String content = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateContent(); + Integer templateStatus = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getStatusCode(); + String auditReason = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getReviewReply(); + + return new SmsTemplateRespDTO().setId(templateId).setContent(content) + .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason); + } + + private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { + + QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); + + smsTemplateResponse.setRequestId(resJson.getJSONObject("Response").getStr("RequestId")); + + smsTemplateResponse.setDescribeTemplateStatusSet(new ArrayList<>()); + + QuerySmsTemplateResponse.TemplateInfo templateInfo = new QuerySmsTemplateResponse.TemplateInfo(); + + Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").getFirst(); + + JSONObject statusJSON = new JSONObject(statusObject); + + templateInfo.setTemplateContent(statusJSON.get("TemplateContent").toString()); + + templateInfo.setStatusCode(Integer.parseInt(statusJSON.get("StatusCode").toString())); + + templateInfo.setReviewReply(statusJSON.get("ReviewReply").toString()); + + templateInfo.setTemplateId(Integer.parseInt(statusJSON.get("TemplateId").toString())); + + smsTemplateResponse.getDescribeTemplateStatusSet().add(templateInfo); + + return smsTemplateResponse; } @VisibleForTesting @@ -146,6 +245,45 @@ public class TencentSmsClient extends AbstractSmsClient { } } + @Data + public static class SmsResponse { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 厂商原返回体 + */ + private Object data; + + } + + + /** + *

类名: QuerySmsTemplateResponse + *

说明: sms模板查询返回信息 + * + * @author :scholar + * 2024/07/17 0:25 + **/ + @Data + public static class QuerySmsTemplateResponse { + private List DescribeTemplateStatusSet; + private String RequestId; + @Data + static class TemplateInfo { + private String TemplateName; + private Integer TemplateId; + private Integer International; + private String ReviewReply; + private long CreateTime; + private String TemplateContent; + private Integer StatusCode; + } + } + @Data private static class SmsReceiveStatus { From 30c6f1eb7785871f87da8aa34d68063547836d5a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 7 Aug 2024 13:03:42 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E5=8E=BB=E9=99=A4=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E4=BA=91=E7=9F=AD=E4=BF=A1=E7=9A=84=20maven=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 22 - .../yudao-module-system-biz/pom.xml | 13 - .../framework/sms/core/client/SmsClient.java | 2 + .../sms/core/client/impl/AliyunSmsClient.java | 375 +++++------------- .../core/client/impl/AliyunSmsClient_old.java | 349 ++++++++++++++++ .../core/client/impl/AliyunSmsClientTest.java | 184 ++++----- .../sms/core/client/impl/SmsClientTests.java | 67 +++- 7 files changed, 609 insertions(+), 403 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 667258250..dfa925c8d 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -69,8 +69,6 @@ 4.11.0 2.15.1 8.5.7 - 4.6.4 - 2.2.1 3.1.880 2.0.5 1.7.8 @@ -548,26 +546,6 @@ - - com.aliyun - aliyun-java-sdk-core - ${aliyun-java-sdk-core.version} - - - opentracing-api - io.opentracing - - - opentracing-util - io.opentracing - - - - - com.aliyun - aliyun-java-sdk-dysmsapi - ${aliyun-java-sdk-dysmsapi.version} - com.tencentcloudapi tencentcloud-sdk-java-sms diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml index 082b6b470..f12ca6271 100644 --- a/yudao-module-system/yudao-module-system-biz/pom.xml +++ b/yudao-module-system/yudao-module-system-biz/pom.xml @@ -110,19 +110,6 @@ wx-java-miniapp-spring-boot-starter - - com.aliyun - aliyun-java-sdk-core - - - com.aliyun - aliyun-java-sdk-dysmsapi - - - com.tencentcloudapi - tencentcloud-sdk-java-sms - - com.xingyuv spring-boot-starter-captcha-plus diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java index 46224663c..5d7b80990 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java @@ -46,6 +46,8 @@ public interface SmsClient { /** * 查询指定的短信模板 * + * 如果查询失败,则返回 null 空 + * * @param apiTemplateId 短信 API 的模板编号 * @return 短信模板 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 708683b21..61f8b753e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -1,12 +1,15 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; @@ -17,23 +20,13 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; 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 com.google.common.annotations.VisibleForTesting; -import lombok.Data; import lombok.extern.slf4j.Slf4j; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; 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; -import java.text.SimpleDateFormat; /** * 阿里短信客户端的实现类 @@ -44,6 +37,12 @@ import java.text.SimpleDateFormat; @Slf4j public class AliyunSmsClient extends AbstractSmsClient { + private static final String URL = "https://dysmsapi.aliyuncs.com"; + private static final String HOST = "dysmsapi.aliyuncs.com"; + private static final String VERSION = "2017-05-25"; + + private static final String RESPONSE_CODE_SUCCESS = "OK"; + public AliyunSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); @@ -52,138 +51,66 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override protected void doInit() { -// IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); -// client = new DefaultAcsClient(profile); } @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms TreeMap queryParam = new TreeMap<>(); queryParam.put("PhoneNumbers",mobile); - queryParam.put("SignName",properties.getSignature()); - queryParam.put("TemplateCode",apiTemplateId); - queryParam.put("TemplateParam",JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("SignName", properties.getSignature()); + queryParam.put("TemplateCode", apiTemplateId); + queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("OutId", sendLogId); + JSONObject response = request("sendSms", queryParam); - JSONObject response = sendSmsRequest(queryParam,"sendSms"); - SmsResponse smsResponse = getSmsSendResponse(response); - - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); - } - - JSONObject sendSmsRequest(TreeMap queryParam,String apiName) throws IOException, URISyntaxException { - - // ************* 步骤 1:拼接规范请求串 ************* - String url = "https://dysmsapi.aliyuncs.com"; //APP接入地址+接口访问URI - String httpMethod = "POST"; // 请求方式 - String canonicalUri = "/"; - // 请求参数,当请求的查询字符串为空时,使用空字符串作为规范化查询字符串 - StringBuilder canonicalQueryString = new StringBuilder(); - queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { - // 如果canonicalQueryString已经不是空的,则在查询参数前添加"&" - if (!canonicalQueryString.isEmpty()) { - canonicalQueryString.append("&"); - } - canonicalQueryString.append(queryPart); - System.out.println("canonicalQueryString=========>\n" + canonicalQueryString); - }); - - SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); - String SdfTime = SDF.format(new Date()); - String randomUUID = UUID.randomUUID().toString(); - - TreeMap headers = new TreeMap<>(); - headers.put("host", "dysmsapi.aliyuncs.com"); - headers.put("x-acs-action", apiName); - headers.put("x-acs-version", "2017-05-25"); - headers.put("x-acs-date", SdfTime); - headers.put("x-acs-signature-nonce", randomUUID); -// headers.put("content-type", "application/json;charset=utf-8"); - - // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 - StringBuilder canonicalHeaders = new StringBuilder(); - // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 - StringBuilder signedHeadersSb = new StringBuilder(); - headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> { - String lowerKey = entry.getKey().toLowerCase(); - String value = String.valueOf(entry.getValue()).trim(); - canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); - signedHeadersSb.append(lowerKey).append(";"); - }); - String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); - - String body = "";//短信API为RPC接口,query parameters在uri中拼接,因此request body如果没有特殊要求,设置为空。 - String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); - - - String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; - System.out.println("canonicalRequest=========>\n" + canonicalRequest); - - // ************* 步骤 2:拼接待签名字符串 ************* - String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); - String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); - - // ************* 步骤 4:拼接 Authorization ************* - String authorization = "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + ", " - + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; - headers.put("Authorization", authorization); - - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* -// url = url + canonicalUri; - String urlWithParams = url + "?" + URLUtil.buildQuery(queryParam, null); - - HttpResponse response = HttpRequest.post(urlWithParams) - .addHeaders(headers) - .body(body) - .execute(); -// URIBuilder uriBuilder = new URIBuilder(url); -// // 添加请求参数 -// for (Map.Entry entry : queryParam.entrySet()) { -// uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); -// } -// HttpUriRequest httpRequest = new HttpPost(uriBuilder.build()); -//// HttpPost httpPost = new HttpPost(uriBuilder.build()); -//// httpRequest = httpPost; -// -// // 添加http请求头 -// for (Map.Entry entry : headers.entrySet()) { -// httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); -// } -// -// // 发送请求 -// CloseableHttpClient httpClient = HttpClients.createDefault(); -// CloseableHttpResponse response = httpClient.execute(httpRequest); - System.out.println("getEntity====="+response.body()); - System.out.println("response====="+response); - - return JSONUtil.parseObj(response.body()); + // 2. 解析请求 + return new SmsSendRespDTO() + .setSuccess(Objects.equals(response.getStr("Code"), RESPONSE_CODE_SUCCESS)) + .setSerialNo(response.getStr("BizId")) + .setApiRequestId(response.getStr("RequestId")) + .setApiCode(response.getStr("Code")) + .setApiMsg(response.getStr("Message")); } @Override public List parseSmsReceiveStatus(String text) { - List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess()) - .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()) - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime()) - .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()))); + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess(statusObj.getBool("success")) // 是否接收成功 + .setErrorCode(statusObj.getStr("err_code")) // 状态报告编码 + .setErrorMsg(statusObj.getStr("err_msg")) // 状态报告说明 + .setMobile(statusObj.getStr("phone_number")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("report_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("biz_id")) // 发送序列号 + .setLogId(statusObj.getLong("out_id")); // 用户序列号 + }); } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate TreeMap queryParam = new TreeMap<>(); - queryParam.put("TemplateCode",apiTemplateId); - - JSONObject response = sendSmsRequest(queryParam,"QuerySmsTemplate"); - QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(response); - return new SmsTemplateRespDTO().setId(smsTemplateResponse.getTemplateCode()).setContent(smsTemplateResponse.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(smsTemplateResponse.getTemplateStatus())).setAuditReason(smsTemplateResponse.getReason()); + queryParam.put("TemplateCode", apiTemplateId); + JSONObject response = request("QuerySmsTemplate", queryParam); + // 2.1 请求失败 + String code = response.getStr("Code"); + if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) { + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); + return null; + } + // 2.2 请求成功 + return new SmsTemplateRespDTO().setId(apiTemplateId) + .setContent(response.getStr("TemplateContent")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) + .setAuditReason(response.getStr("Reason")); } @VisibleForTesting @@ -196,154 +123,72 @@ public class AliyunSmsClient extends AbstractSmsClient { } } - /** - * 对指定的字符串进行URL编码。 - * 使用UTF-8编码字符集对字符串进行编码,并对特定的字符进行替换,以符合URL编码规范。 + * 请求阿里云短信 * - * @param str 需要进行URL编码的字符串。 - * @return 编码后的字符串。其中,加号"+"被替换为"%20",星号"*"被替换为"%2A",波浪号"%7E"被替换为"~"。 + * @see V3 版本请求体&签名机制 + * @param apiName 请求的 API 名称 + * @param queryParams 请求参数 + * @return 请求结果 */ - public static String percentCode(String str) { - if (str == null) { - throw new IllegalArgumentException("输入字符串不可为null"); - } - try { - return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8编码不被支持", e); + private JSONObject request(String apiName, TreeMap queryParams) { + // 1. 请求参数 + String queryString = queryParams.entrySet().stream() + .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) + .collect(Collectors.joining("&")); + + // 2.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("host", HOST); + headers.put("x-acs-version", VERSION); + headers.put("x-acs-action", apiName); + headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date())); + headers.put("x-acs-signature-nonce", IdUtil.randomUUID()); + // 2.2 构建签名 Header + StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") + || entry.getKey().equalsIgnoreCase("host") + || entry.getKey().equalsIgnoreCase("content-type")) + .sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + canonicalHeaders.append(lowerKey).append(":").append(String.valueOf(entry.getValue()).trim()).append("\n"); + signedHeadersBuilder.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1); + + // 3. 请求 Body + String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。 + String hashedRequestBody = DigestUtil.sha256Hex(requestBody); + + // 4. 构建 Authorization 签名 + String hashedCanonicalRequest = DigestUtil.sha256Hex("POST" // httpMethod + + "\n" + "/" // canonicalUri + + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody); + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 5. 发起请求 + String urlWithParams = URL + "?" + URLUtil.buildQuery(queryParams, null); + try (HttpResponse response = HttpRequest.post(urlWithParams).addHeaders(headers).body(requestBody).execute()) { + return JSONUtil.parseObj(response.body()); } } - private SmsResponse getSmsSendResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); - smsResponse.setData(resJson); -// smsResponse.setConfigId(getConfigId()); - return smsResponse; - } - - private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { - - QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); - - smsTemplateResponse.setRequestId(resJson.getStr("RequestId")); - smsTemplateResponse.setTemplateContent(resJson.getStr("TemplateContent")); - smsTemplateResponse.setReason(resJson.getStr("Reason")); - smsTemplateResponse.setTemplateStatus(resJson.getInt("TemplateStatus")); - - return smsTemplateResponse; - } - /** - *

类名: SmsResponse - *

说明: 发送短信返回信息 + * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范 * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class SmsResponse { - - /** - * 是否成功 - */ - private boolean success; - - /** - * 厂商原返回体 - */ - private Object data; - - /** - * 配置标识名 如未配置取对应渠道名例如 Alibaba - */ - private String configId; - } - - - /** - *

类名: QuerySmsTemplateResponse - *

说明: sms模板查询返回信息 - * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class QuerySmsTemplateResponse { - private String requestId; - private String code; - private String message; - private Integer templateStatus; - private String reason; - private String templateCode; - private Integer templateType; - private String templateName; - private String templateContent; - private String createDate; - } - - /** - * 短信接收状态 - * - * 参见 文档 - * - * @author 润普源码 + * @param str 需要进行URL编码的字符串 + * @return 编码后的字符串 */ - @Data - public static class SmsReceiveStatus { - - /** - * 手机号 - */ - @JsonProperty("phone_number") - private String phoneNumber; - /** - * 发送时间 - */ - @JsonProperty("send_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime sendTime; - /** - * 状态报告时间 - */ - @JsonProperty("report_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime reportTime; - /** - * 是否接收成功 - */ - private Boolean success; - /** - * 状态报告说明 - */ - @JsonProperty("err_msg") - private String errMsg; - /** - * 状态报告编码 - */ - @JsonProperty("err_code") - private String errCode; - /** - * 发送序列号 - */ - @JsonProperty("biz_id") - private String bizId; - /** - * 用户序列号 - * - * 这里我们传递的是 SysSmsLogDO 的日志编号 - */ - @JsonProperty("out_id") - private String outId; - /** - * 短信长度,例如说 1、2、3 - * - * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送 - */ - @JsonProperty("sms_size") - private Integer smsSize; - + private static String percentCode(String str) { + Assert.notNull(str, "str 不能为空"); + return URLUtil.encode(str) + .replace("+", "%20") // 加号 "+" 被替换为 "%20" + .replace("*", "%2A") // 星号 "*" 被替换为 "%2A" + .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~" } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java new file mode 100644 index 000000000..37edc284b --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java @@ -0,0 +1,349 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; +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.MapUtils; +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; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +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 com.google.common.annotations.VisibleForTesting; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.*; + +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; + +/** + * 阿里短信客户端的实现类 + * + * @author zzf + * @since 2021/1/25 14:17 + */ +@Slf4j +public class AliyunSmsClient_old extends AbstractSmsClient { + + public AliyunSmsClient_old(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + @Override + protected void doInit() { +// IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); +// client = new DefaultAcsClient(profile); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + + TreeMap queryParam = new TreeMap<>(); + queryParam.put("PhoneNumbers",mobile); + queryParam.put("SignName",properties.getSignature()); + queryParam.put("TemplateCode",apiTemplateId); + queryParam.put("TemplateParam",JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + + JSONObject response = sendSmsRequest(queryParam,"sendSms"); + SmsResponse smsResponse = getSmsSendResponse(response); + + return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + } + + JSONObject sendSmsRequest(TreeMap queryParam,String apiName) throws IOException, URISyntaxException { + + // ************* 步骤 1:拼接规范请求串 ************* + String url = "https://dysmsapi.aliyuncs.com"; //APP接入地址+接口访问URI + String httpMethod = "POST"; // 请求方式 + String canonicalUri = "/"; + // 请求参数,当请求的查询字符串为空时,使用空字符串作为规范化查询字符串 + StringBuilder canonicalQueryString = new StringBuilder(); + queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { + // 如果canonicalQueryString已经不是空的,则在查询参数前添加"&" + if (!canonicalQueryString.isEmpty()) { + canonicalQueryString.append("&"); + } + canonicalQueryString.append(queryPart); + System.out.println("canonicalQueryString=========>\n" + canonicalQueryString); + }); + + SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); + String SdfTime = SDF.format(new Date()); + String randomUUID = UUID.randomUUID().toString(); + + TreeMap headers = new TreeMap<>(); + headers.put("host", "dysmsapi.aliyuncs.com"); + headers.put("x-acs-action", apiName); + headers.put("x-acs-version", "2017-05-25"); + headers.put("x-acs-date", SdfTime); + headers.put("x-acs-signature-nonce", randomUUID); +// headers.put("content-type", "application/json;charset=utf-8"); + + // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder canonicalHeaders = new StringBuilder(); + // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + StringBuilder signedHeadersSb = new StringBuilder(); + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + String value = String.valueOf(entry.getValue()).trim(); + canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); + signedHeadersSb.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); + + String body = "";//短信API为RPC接口,query parameters在uri中拼接,因此request body如果没有特殊要求,设置为空。 + String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); + + + String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + System.out.println("canonicalRequest=========>\n" + canonicalRequest); + + // ************* 步骤 2:拼接待签名字符串 ************* + String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + + // ************* 步骤 3:计算签名 ************* + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); + + // ************* 步骤 4:拼接 Authorization ************* + String authorization = "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + headers.put("Authorization", authorization); + + // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* +// url = url + canonicalUri; + String urlWithParams = url + "?" + URLUtil.buildQuery(queryParam, null); + + HttpResponse response = HttpRequest.post(urlWithParams) + .addHeaders(headers) + .body(body) + .execute(); +// URIBuilder uriBuilder = new URIBuilder(url); +// // 添加请求参数 +// for (Map.Entry entry : queryParam.entrySet()) { +// uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); +// } +// HttpUriRequest httpRequest = new HttpPost(uriBuilder.build()); +//// HttpPost httpPost = new HttpPost(uriBuilder.build()); +//// httpRequest = httpPost; +// +// // 添加http请求头 +// for (Map.Entry entry : headers.entrySet()) { +// httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); +// } +// +// // 发送请求 +// CloseableHttpClient httpClient = HttpClients.createDefault(); +// CloseableHttpResponse response = httpClient.execute(httpRequest); + System.out.println("getEntity====="+response.body()); + System.out.println("response====="+response); + + return JSONUtil.parseObj(response.body()); + } + + @Override + public List parseSmsReceiveStatus(String text) { + List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); + return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess()) + .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()) + .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime()) + .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()))); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + + TreeMap queryParam = new TreeMap<>(); + queryParam.put("TemplateCode",apiTemplateId); + + JSONObject response = sendSmsRequest(queryParam,"QuerySmsTemplate"); + QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(response); + return new SmsTemplateRespDTO().setId(smsTemplateResponse.getTemplateCode()).setContent(smsTemplateResponse.getTemplateContent()) + .setAuditStatus(convertSmsTemplateAuditStatus(smsTemplateResponse.getTemplateStatus())).setAuditReason(smsTemplateResponse.getReason()); + + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(Integer templateStatus) { + switch (templateStatus) { + case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus)); + } + } + + + /** + * 对指定的字符串进行URL编码。 + * 使用UTF-8编码字符集对字符串进行编码,并对特定的字符进行替换,以符合URL编码规范。 + * + * @param str 需要进行URL编码的字符串。 + * @return 编码后的字符串。其中,加号"+"被替换为"%20",星号"*"被替换为"%2A",波浪号"%7E"被替换为"~"。 + */ + public static String percentCode(String str) { + if (str == null) { + throw new IllegalArgumentException("输入字符串不可为null"); + } + try { + return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8编码不被支持", e); + } + } + + private SmsResponse getSmsSendResponse(JSONObject resJson) { + SmsResponse smsResponse = new SmsResponse(); + smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); + smsResponse.setData(resJson); +// smsResponse.setConfigId(getConfigId()); + return smsResponse; + } + + private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { + + QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); + + smsTemplateResponse.setRequestId(resJson.getStr("RequestId")); + smsTemplateResponse.setTemplateContent(resJson.getStr("TemplateContent")); + smsTemplateResponse.setReason(resJson.getStr("Reason")); + smsTemplateResponse.setTemplateStatus(resJson.getInt("TemplateStatus")); + + return smsTemplateResponse; + } + + /** + *

类名: SmsResponse + *

说明: 发送短信返回信息 + * + * @author :scholar + * 2024/07/17 0:25 + **/ + @Data + public static class SmsResponse { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 厂商原返回体 + */ + private Object data; + + /** + * 配置标识名 如未配置取对应渠道名例如 Alibaba + */ + private String configId; + } + + + /** + *

类名: QuerySmsTemplateResponse + *

说明: sms模板查询返回信息 + * + * @author :scholar + * 2024/07/17 0:25 + **/ + @Data + public static class QuerySmsTemplateResponse { + private String requestId; + private String code; + private String message; + private Integer templateStatus; + private String reason; + private String templateCode; + private Integer templateType; + private String templateName; + private String templateContent; + private String createDate; + } + + /** + * 短信接收状态 + * + * 参见 文档 + * + * @author 润普源码 + */ + @Data + public static class SmsReceiveStatus { + + /** + * 手机号 + */ + @JsonProperty("phone_number") + private String phoneNumber; + /** + * 发送时间 + */ + @JsonProperty("send_time") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) + private LocalDateTime sendTime; + /** + * 状态报告时间 + */ + @JsonProperty("report_time") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) + private LocalDateTime reportTime; + /** + * 是否接收成功 + */ + private Boolean success; + /** + * 状态报告说明 + */ + @JsonProperty("err_msg") + private String errMsg; + /** + * 状态报告编码 + */ + @JsonProperty("err_code") + private String errCode; + /** + * 发送序列号 + */ + @JsonProperty("biz_id") + private String bizId; + /** + * 用户序列号 + * + * 这里我们传递的是 SysSmsLogDO 的日志编号 + */ + @JsonProperty("out_id") + private String outId; + /** + * 短信长度,例如说 1、2、3 + * + * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送 + */ + @JsonProperty("sms_size") + private Integer smsSize; + + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java index ac26d139b..bc5a47140 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java @@ -1,36 +1,21 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; -import cn.hutool.core.util.ReflectUtil; -import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 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.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; -import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; import org.mockito.InjectMocks; -import org.mockito.Mock; import java.time.LocalDateTime; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; +// TODO 芋艿:需要优化 /** - * {@link AliyunSmsClient} 的单元测试 + * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient_old} 的单元测试 * * @author 芋道源码 */ @@ -44,9 +29,6 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { @InjectMocks private final AliyunSmsClient smsClient = new AliyunSmsClient(properties); - @Mock - private IAcsClient client; - @Test public void testDoInit() { // 准备参数 @@ -54,68 +36,66 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { // 调用 smsClient.doInit(); - // 断言 - assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient")); } - @Test - public void tesSendSms_success() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK")); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(mobile, acsRequest.getPhoneNumbers()); - assertEquals(properties.getSignature(), acsRequest.getSignName()); - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); - assertEquals(sendLogId.toString(), acsRequest.getOutId()); - return true; - }))).thenReturn(response); +// @Test +// public void tesSendSms_success() throws Throwable { +// // 准备参数 +// Long sendLogId = randomLongId(); +// String mobile = randomString(); +// String apiTemplateId = randomString(); +// List> templateParams = Lists.newArrayList( +// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); +// // mock 方法 +// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK")); +// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { +// assertEquals(mobile, acsRequest.getPhoneNumbers()); +// assertEquals(properties.getSignature(), acsRequest.getSignName()); +// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); +// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); +// assertEquals(sendLogId.toString(), acsRequest.getOutId()); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, +// apiTemplateId, templateParams); +// // 断言 +// assertTrue(result.getSuccess()); +// assertEquals(response.getRequestId(), result.getApiRequestId()); +// assertEquals(response.getCode(), result.getApiCode()); +// assertEquals(response.getMessage(), result.getApiMsg()); +// assertEquals(response.getBizId(), result.getSerialNo()); +// } - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, - apiTemplateId, templateParams); - // 断言 - assertTrue(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getCode(), result.getApiCode()); - assertEquals(response.getMessage(), result.getApiMsg()); - assertEquals(response.getBizId(), result.getSerialNo()); - } - - @Test - public void tesSendSms_fail() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR")); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(mobile, acsRequest.getPhoneNumbers()); - assertEquals(properties.getSignature(), acsRequest.getSignName()); - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); - assertEquals(sendLogId.toString(), acsRequest.getOutId()); - return true; - }))).thenReturn(response); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertFalse(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getCode(), result.getApiCode()); - assertEquals(response.getMessage(), result.getApiMsg()); - assertEquals(response.getBizId(), result.getSerialNo()); - } +// @Test +// public void tesSendSms_fail() throws Throwable { +// // 准备参数 +// Long sendLogId = randomLongId(); +// String mobile = randomString(); +// String apiTemplateId = randomString(); +// List> templateParams = Lists.newArrayList( +// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); +// // mock 方法 +// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR")); +// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { +// assertEquals(mobile, acsRequest.getPhoneNumbers()); +// assertEquals(properties.getSignature(), acsRequest.getSignName()); +// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); +// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); +// assertEquals(sendLogId.toString(), acsRequest.getOutId()); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); +// // 断言 +// assertFalse(result.getSuccess()); +// assertEquals(response.getRequestId(), result.getApiRequestId()); +// assertEquals(response.getCode(), result.getApiCode()); +// assertEquals(response.getMessage(), result.getApiMsg()); +// assertEquals(response.getBizId(), result.getSerialNo()); +// } @Test public void testParseSmsReceiveStatus() { @@ -149,28 +129,28 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { assertEquals(67890L, statuses.get(0).getLogId()); } - @Test - public void testGetSmsTemplate() throws Throwable { - // 准备参数 - String apiTemplateId = randomString(); - // mock 方法 - QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> { - o.setCode("OK"); - o.setTemplateStatus(1); // 设置模板通过 - }); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - return true; - }))).thenReturn(response); - - // 调用 - SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); - // 断言 - assertEquals(response.getTemplateCode(), result.getId()); - assertEquals(response.getTemplateContent(), result.getContent()); - assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); - assertEquals(response.getReason(), result.getAuditReason()); - } +// @Test +// public void testGetSmsTemplate() throws Throwable { +// // 准备参数 +// String apiTemplateId = randomString(); +// // mock 方法 +// QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> { +// o.setCode("OK"); +// o.setTemplateStatus(1); // 设置模板通过 +// }); +// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { +// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); +// // 断言 +// assertEquals(response.getTemplateCode(), result.getId()); +// assertEquals(response.getTemplateContent(), result.getContent()); +// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); +// assertEquals(response.getReason(), result.getAuditReason()); +// } @Test public void testConvertSmsTemplateAuditStatus() { diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 677bf986e..13848d33c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.iocoder.yudao.framework.common.core.KeyValue; +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.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -17,7 +19,7 @@ public class SmsClientTests { @Test @Disabled - public void testHuaweiSmsClient() throws Throwable { + public void testHuaweiSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("123") .setApiSecret("456"); @@ -33,4 +35,67 @@ public class SmsClientTests { System.out.println(smsSendRespDTO); } + // ========== 阿里云 ========== + + @Test + @Disabled + public void testAliyunSmsClient_getSmsTemplate() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") + .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + AliyunSmsClient client = new AliyunSmsClient(properties); + // 准备参数 + String apiTemplateId = "SMS_207945135"; + // 调用 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); + // 打印结果 + System.out.println(template); + } + + @Test + @Disabled + public void testAliyunSmsClient_sendSms() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") + .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + AliyunSmsClient client = new AliyunSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "17321315478"; + String apiTemplateId = "SMS_207945135"; + // 调用 + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024"))); + // 打印结果 + System.out.println(sendRespDTO); + } + + @Test + @Disabled + public void testAliyunSmsClient_parseSmsReceiveStatus() { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") + .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + AliyunSmsClient client = new AliyunSmsClient(properties); + // 准备参数 + String text = "[\n" + + " {\n" + + " \"phone_number\" : \"13900000001\",\n" + + " \"send_time\" : \"2017-01-01 11:12:13\",\n" + + " \"report_time\" : \"2017-02-02 22:23:24\",\n" + + " \"success\" : true,\n" + + " \"err_code\" : \"DELIVERED\",\n" + + " \"err_msg\" : \"用户接收成功\",\n" + + " \"sms_size\" : \"1\",\n" + + " \"biz_id\" : \"12345\",\n" + + " \"out_id\" : \"67890\"\n" + + " }\n" + + "]"; + // mock 方法 + + // 调用 + List statuses = client.parseSmsReceiveStatus(text); + // 打印结果 + System.out.println(statuses); + } + } From 24b15949766297f37868e0d9eec9bb63dc376b85 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 7 Aug 2024 17:09:16 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91MALL-TRADE=EF=BC=9A=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E4=BF=A1=E6=81=AF=E8=BF=94=E5=9B=9E=E7=A7=AF?= =?UTF-8?q?=E5=88=86=E7=9B=B8=E5=85=B3=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/order/vo/AppTradeOrderSettlementRespVO.java | 4 ++-- .../trade/service/price/bo/TradePriceCalculateRespBO.java | 6 +++++- .../price/calculator/TradePointUsePriceCalculator.java | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java index 95f9fc8fa..9aab1b68b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo; import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.validation.constraints.NotNull; import java.util.List; @Schema(description = "用户 App - 交易订单结算信息 Response VO") @@ -26,7 +26,7 @@ public class AppTradeOrderSettlementRespVO { private Address address; @Schema(description = "已使用的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer usedPoint; + private Integer usePoint; @Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Integer totalPoint; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java index 93867f1e4..b7482407c 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -48,13 +48,17 @@ public class TradePriceCalculateRespBO { */ private Long couponId; + /** + * 会员剩余积分 + */ + private Integer totalPoint; /** * 使用的积分 */ private Integer usePoint; /** - * 使用的积分 + * 赠送的积分 */ private Integer givePoint; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java index 40b7450fd..bbb92b3ad 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java @@ -9,11 +9,11 @@ import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -39,6 +39,9 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { // 默认使用积分为 0 result.setUsePoint(0); + MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); + result.setTotalPoint(user.getPoint()); // 设置会员总积分 + // 1.1 校验是否使用积分 if (!BooleanUtil.isTrue(param.getPointStatus())) { result.setUsePoint(0); @@ -50,7 +53,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { return; } // 1.3 校验用户积分余额 - MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); if (user.getPoint() == null || user.getPoint() <= 0) { return; } From 4322e2f8e148ff2d428f4ea68761ef4bb39ae355 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 7 Aug 2024 17:31:46 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E8=85=BE=E8=AE=AF=E4=BA=91=E7=9F=AD=E4=BF=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=BF=98=E9=9C=80=E8=A6=81=20tencentcloud-sd?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-system/yudao-module-system-biz/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml index f12ca6271..0da7c1175 100644 --- a/yudao-module-system/yudao-module-system-biz/pom.xml +++ b/yudao-module-system/yudao-module-system-biz/pom.xml @@ -110,6 +110,11 @@ wx-java-miniapp-spring-boot-starter + + com.tencentcloudapi + tencentcloud-sdk-java-sms + + com.xingyuv spring-boot-starter-captcha-plus From 8eb82f0b591cb0237b1b526a446964c4db21195d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 7 Aug 2024 21:20:29 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E5=8E=BB=E9=99=A4=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E4=BA=91=E7=9F=AD=E4=BF=A1=E7=9A=84=20maven=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 9 - .../core/client/impl/TencentSmsClient.java | 5 +- .../client/impl/TencentSmsClientTest.java | 238 +++++++++--------- 3 files changed, 115 insertions(+), 137 deletions(-) diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index dfa925c8d..3cdc79650 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -69,7 +69,6 @@ 4.11.0 2.15.1 8.5.7 - 3.1.880 2.0.5 1.7.8 2.12.2 @@ -545,14 +544,6 @@ ${minio.version} - - - com.tencentcloudapi - tencentcloud-sdk-java-sms - ${tencentcloud-sdk-java.version} - - - com.xingyuv spring-boot-starter-justauth diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index f18598b07..ff3e5ca96 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -18,11 +18,11 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; +import jakarta.xml.bind.DatatypeConverter; import lombok.Data; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import javax.xml.bind.DatatypeConverter; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.time.LocalDateTime; @@ -33,6 +33,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. 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 建议参考 AliyunSmsClient 优化下 /** * 腾讯云短信功能实现 * @@ -218,7 +219,7 @@ public class TencentSmsClient extends AbstractSmsClient { QuerySmsTemplateResponse.TemplateInfo templateInfo = new QuerySmsTemplateResponse.TemplateInfo(); - Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").getFirst(); + Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").get(0); JSONObject statusJSON = new JSONObject(statusObject); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java index e93435f4d..6d621e170 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java @@ -1,36 +1,22 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.util.ReflectUtil; -import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; 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.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.google.common.collect.Lists; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse; -import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus; -import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; -import com.tencentcloudapi.sms.v20210111.models.SendStatus; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; +// TODO @芋艿:补全单测 /** * {@link TencentSmsClient} 的单元测试 * @@ -73,87 +59,87 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client")); } - @Test - public void testDoSendSms_success() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); - String requestId = randomString(); - String serialNo = randomString(); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> { - o.setRequestId(requestId); - SendStatus[] sendStatuses = new SendStatus[1]; - o.setSendStatusSet(sendStatuses); - SendStatus sendStatus = new SendStatus(); - sendStatuses[0] = sendStatus; - sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS); - sendStatus.setMessage("send success"); - sendStatus.setSerialNo(serialNo); - }); - when(client.SendSms(argThat(request -> { - assertEquals(mobile, request.getPhoneNumberSet()[0]); - assertEquals(properties.getSignature(), request.getSignName()); - assertEquals(apiTemplateId, request.getTemplateId()); - assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)), - toJsonString(request.getTemplateParamSet())); - assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId")); - return true; - }))).thenReturn(response); +// @Test +// public void testDoSendSms_success() throws Throwable { +// // 准备参数 +// Long sendLogId = randomLongId(); +// String mobile = randomString(); +// String apiTemplateId = randomString(); +// List> templateParams = Lists.newArrayList( +// new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); +// String requestId = randomString(); +// String serialNo = randomString(); +// // mock 方法 +// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> { +// o.setRequestId(requestId); +// SendStatus[] sendStatuses = new SendStatus[1]; +// o.setSendStatusSet(sendStatuses); +// SendStatus sendStatus = new SendStatus(); +// sendStatuses[0] = sendStatus; +// sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS); +// sendStatus.setMessage("send success"); +// sendStatus.setSerialNo(serialNo); +// }); +// when(client.SendSms(argThat(request -> { +// assertEquals(mobile, request.getPhoneNumberSet()[0]); +// assertEquals(properties.getSignature(), request.getSignName()); +// assertEquals(apiTemplateId, request.getTemplateId()); +// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)), +// toJsonString(request.getTemplateParamSet())); +// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId")); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); +// // 断言 +// assertTrue(result.getSuccess()); +// assertEquals(response.getRequestId(), result.getApiRequestId()); +// assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); +// assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); +// assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); +// } - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertTrue(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); - assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); - assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); - } - - @Test - public void testDoSendSms_fail() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); - String requestId = randomString(); - String serialNo = randomString(); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> { - o.setRequestId(requestId); - SendStatus[] sendStatuses = new SendStatus[1]; - o.setSendStatusSet(sendStatuses); - SendStatus sendStatus = new SendStatus(); - sendStatuses[0] = sendStatus; - sendStatus.setCode("ERROR"); - sendStatus.setMessage("send success"); - sendStatus.setSerialNo(serialNo); - }); - when(client.SendSms(argThat(request -> { - assertEquals(mobile, request.getPhoneNumberSet()[0]); - assertEquals(properties.getSignature(), request.getSignName()); - assertEquals(apiTemplateId, request.getTemplateId()); - assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)), - toJsonString(request.getTemplateParamSet())); - assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId")); - return true; - }))).thenReturn(response); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertFalse(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); - assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); - assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); - } +// @Test +// public void testDoSendSms_fail() throws Throwable { +// // 准备参数 +// Long sendLogId = randomLongId(); +// String mobile = randomString(); +// String apiTemplateId = randomString(); +// List> templateParams = Lists.newArrayList( +// new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); +// String requestId = randomString(); +// String serialNo = randomString(); +// // mock 方法 +// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> { +// o.setRequestId(requestId); +// SendStatus[] sendStatuses = new SendStatus[1]; +// o.setSendStatusSet(sendStatuses); +// SendStatus sendStatus = new SendStatus(); +// sendStatuses[0] = sendStatus; +// sendStatus.setCode("ERROR"); +// sendStatus.setMessage("send success"); +// sendStatus.setSerialNo(serialNo); +// }); +// when(client.SendSms(argThat(request -> { +// assertEquals(mobile, request.getPhoneNumberSet()[0]); +// assertEquals(properties.getSignature(), request.getSignName()); +// assertEquals(apiTemplateId, request.getTemplateId()); +// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)), +// toJsonString(request.getTemplateParamSet())); +// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId")); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); +// // 断言 +// assertFalse(result.getSuccess()); +// assertEquals(response.getRequestId(), result.getApiRequestId()); +// assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); +// assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); +// assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); +// } @Test public void testParseSmsReceiveStatus() { @@ -185,35 +171,35 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { assertEquals(67890L, statuses.get(0).getLogId()); } - @Test - public void testGetSmsTemplate() throws Throwable { - // 准备参数 - Long apiTemplateId = randomLongId(); - String requestId = randomString(); - - // mock 方法 - DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> { - DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1]; - DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus(); - templateStatus.setTemplateId(apiTemplateId); - templateStatus.setStatusCode(0L);// 设置模板通过 - describeTemplateListStatuses[0] = templateStatus; - o.setDescribeTemplateStatusSet(describeTemplateListStatuses); - o.setRequestId(requestId); - }); - when(client.DescribeSmsTemplateList(argThat(request -> { - assertEquals(apiTemplateId, request.getTemplateIdSet()[0]); - return true; - }))).thenReturn(response); - - // 调用 - SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString()); - // 断言 - assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId()); - assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent()); - assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); - assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason()); - } +// @Test +// public void testGetSmsTemplate() throws Throwable { +// // 准备参数 +// Long apiTemplateId = randomLongId(); +// String requestId = randomString(); +// +// // mock 方法 +// DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> { +// DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1]; +// DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus(); +// templateStatus.setTemplateId(apiTemplateId); +// templateStatus.setStatusCode(0L);// 设置模板通过 +// describeTemplateListStatuses[0] = templateStatus; +// o.setDescribeTemplateStatusSet(describeTemplateListStatuses); +// o.setRequestId(requestId); +// }); +// when(client.DescribeSmsTemplateList(argThat(request -> { +// assertEquals(apiTemplateId, request.getTemplateIdSet()[0]); +// return true; +// }))).thenReturn(response); +// +// // 调用 +// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString()); +// // 断言 +// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId()); +// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent()); +// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); +// assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason()); +// } @Test public void testConvertSmsTemplateAuditStatus() { From 8775472af7156b7abef2b9d0ffad1939e5a2ad49 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 7 Aug 2024 22:09:31 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E9=97=A8=E5=BA=97=E8=87=AA=E6=8F=90=EF=BC=9A?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=E7=9A=84=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E3=80=81=E4=BB=A5=E5=8F=8A=20todo=20=E8=AF=84=E5=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../price/calculator/TradeDeliveryPriceCalculator.java | 1 + .../price/calculator/TradePointUsePriceCalculator.java | 6 ++---- yudao-module-system/yudao-module-system-biz/pom.xml | 5 ----- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index 30558bdb6..d9fed7aeb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -55,6 +55,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { if (param.getDeliveryType() == null) { return; } + // TODO @puhui999:需要校验,是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈 if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { calculateByPickUp(param); } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java index bbb92b3ad..8dc2b30d0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java @@ -37,14 +37,12 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { @Override public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { - // 默认使用积分为 0 - result.setUsePoint(0); + // 0. 初始化积分 MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()); - result.setTotalPoint(user.getPoint()); // 设置会员总积分 + result.setTotalPoint(user.getPoint()).setUsePoint(0); // 1.1 校验是否使用积分 if (!BooleanUtil.isTrue(param.getPointStatus())) { - result.setUsePoint(0); return; } // 1.2 校验积分抵扣是否开启 diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml index 0da7c1175..f12ca6271 100644 --- a/yudao-module-system/yudao-module-system-biz/pom.xml +++ b/yudao-module-system/yudao-module-system-biz/pom.xml @@ -110,11 +110,6 @@ wx-java-miniapp-spring-boot-starter - - com.tencentcloudapi - tencentcloud-sdk-java-sms - - com.xingyuv spring-boot-starter-captcha-plus From 2f38261de837b55c86b34053768bed05139e1743 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Thu, 8 Aug 2024 22:16:23 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1fix=20urlencode=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/AliyunSmsClient.java | 373 ++++++------------ 1 file changed, 114 insertions(+), 259 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 708683b21..d45f3051c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -1,12 +1,15 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; @@ -17,23 +20,15 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; 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 com.google.common.annotations.VisibleForTesting; -import lombok.Data; import lombok.extern.slf4j.Slf4j; -import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; import java.net.URLEncoder; -import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; 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; -import java.text.SimpleDateFormat; /** * 阿里短信客户端的实现类 @@ -44,6 +39,12 @@ import java.text.SimpleDateFormat; @Slf4j public class AliyunSmsClient extends AbstractSmsClient { + private static final String URL = "https://dysmsapi.aliyuncs.com"; + private static final String HOST = "dysmsapi.aliyuncs.com"; + private static final String VERSION = "2017-05-25"; + + private static final String RESPONSE_CODE_SUCCESS = "OK"; + public AliyunSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); @@ -52,138 +53,68 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override protected void doInit() { -// IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); -// client = new DefaultAcsClient(profile); } @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms TreeMap queryParam = new TreeMap<>(); queryParam.put("PhoneNumbers",mobile); - queryParam.put("SignName",properties.getSignature()); - queryParam.put("TemplateCode",apiTemplateId); - queryParam.put("TemplateParam",JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("SignName", properties.getSignature()); + queryParam.put("TemplateCode", apiTemplateId); + queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("OutId", sendLogId); + JSONObject response = request("SendSms", queryParam); - JSONObject response = sendSmsRequest(queryParam,"sendSms"); - SmsResponse smsResponse = getSmsSendResponse(response); - - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); - } - - JSONObject sendSmsRequest(TreeMap queryParam,String apiName) throws IOException, URISyntaxException { - - // ************* 步骤 1:拼接规范请求串 ************* - String url = "https://dysmsapi.aliyuncs.com"; //APP接入地址+接口访问URI - String httpMethod = "POST"; // 请求方式 - String canonicalUri = "/"; - // 请求参数,当请求的查询字符串为空时,使用空字符串作为规范化查询字符串 - StringBuilder canonicalQueryString = new StringBuilder(); - queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { - // 如果canonicalQueryString已经不是空的,则在查询参数前添加"&" - if (!canonicalQueryString.isEmpty()) { - canonicalQueryString.append("&"); - } - canonicalQueryString.append(queryPart); - System.out.println("canonicalQueryString=========>\n" + canonicalQueryString); - }); - - SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); - String SdfTime = SDF.format(new Date()); - String randomUUID = UUID.randomUUID().toString(); - - TreeMap headers = new TreeMap<>(); - headers.put("host", "dysmsapi.aliyuncs.com"); - headers.put("x-acs-action", apiName); - headers.put("x-acs-version", "2017-05-25"); - headers.put("x-acs-date", SdfTime); - headers.put("x-acs-signature-nonce", randomUUID); -// headers.put("content-type", "application/json;charset=utf-8"); - - // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 - StringBuilder canonicalHeaders = new StringBuilder(); - // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 - StringBuilder signedHeadersSb = new StringBuilder(); - headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> { - String lowerKey = entry.getKey().toLowerCase(); - String value = String.valueOf(entry.getValue()).trim(); - canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); - signedHeadersSb.append(lowerKey).append(";"); - }); - String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); - - String body = "";//短信API为RPC接口,query parameters在uri中拼接,因此request body如果没有特殊要求,设置为空。 - String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); - - - String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; - System.out.println("canonicalRequest=========>\n" + canonicalRequest); - - // ************* 步骤 2:拼接待签名字符串 ************* - String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); - String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); - - // ************* 步骤 4:拼接 Authorization ************* - String authorization = "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + ", " - + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; - headers.put("Authorization", authorization); - - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* -// url = url + canonicalUri; - String urlWithParams = url + "?" + URLUtil.buildQuery(queryParam, null); - - HttpResponse response = HttpRequest.post(urlWithParams) - .addHeaders(headers) - .body(body) - .execute(); -// URIBuilder uriBuilder = new URIBuilder(url); -// // 添加请求参数 -// for (Map.Entry entry : queryParam.entrySet()) { -// uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); -// } -// HttpUriRequest httpRequest = new HttpPost(uriBuilder.build()); -//// HttpPost httpPost = new HttpPost(uriBuilder.build()); -//// httpRequest = httpPost; -// -// // 添加http请求头 -// for (Map.Entry entry : headers.entrySet()) { -// httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); -// } -// -// // 发送请求 -// CloseableHttpClient httpClient = HttpClients.createDefault(); -// CloseableHttpResponse response = httpClient.execute(httpRequest); - System.out.println("getEntity====="+response.body()); - System.out.println("response====="+response); - - return JSONUtil.parseObj(response.body()); + // 2. 解析请求 + return new SmsSendRespDTO() + .setSuccess(Objects.equals(response.getStr("Code"), RESPONSE_CODE_SUCCESS)) + .setSerialNo(response.getStr("BizId")) + .setApiRequestId(response.getStr("RequestId")) + .setApiCode(response.getStr("Code")) + .setApiMsg(response.getStr("Message")); } @Override public List parseSmsReceiveStatus(String text) { - List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess()) - .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()) - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime()) - .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()))); + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess(statusObj.getBool("success")) // 是否接收成功 + .setErrorCode(statusObj.getStr("err_code")) // 状态报告编码 + .setErrorMsg(statusObj.getStr("err_msg")) // 状态报告说明 + .setMobile(statusObj.getStr("phone_number")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("report_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("biz_id")) // 发送序列号 + .setLogId(statusObj.getLong("out_id")); // 用户序列号 + }); } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate TreeMap queryParam = new TreeMap<>(); - queryParam.put("TemplateCode",apiTemplateId); + queryParam.put("TemplateCode", apiTemplateId); + JSONObject response = request("QuerySmsTemplate", queryParam); - JSONObject response = sendSmsRequest(queryParam,"QuerySmsTemplate"); - QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(response); - return new SmsTemplateRespDTO().setId(smsTemplateResponse.getTemplateCode()).setContent(smsTemplateResponse.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(smsTemplateResponse.getTemplateStatus())).setAuditReason(smsTemplateResponse.getReason()); + System.out.println("getSmsTemplate response is =====" + response.toString()); + // 2.1 请求失败 + String code = response.getStr("Code"); + if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) { + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); + return null; + } + // 2.2 请求成功 + return new SmsTemplateRespDTO().setId(apiTemplateId) + .setContent(response.getStr("TemplateContent")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) + .setAuditReason(response.getStr("Reason")); } @VisibleForTesting @@ -196,13 +127,68 @@ public class AliyunSmsClient extends AbstractSmsClient { } } + /** + * 请求阿里云短信 + * + * @see V3 版本请求体&签名机制 + * @param apiName 请求的 API 名称 + * @param queryParams 请求参数 + * @return 请求结果 + */ + private JSONObject request(String apiName, TreeMap queryParams) { + // 1. 请求参数 + String queryString = queryParams.entrySet().stream() + .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) + .collect(Collectors.joining("&")); + + // 2.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("host", HOST); + headers.put("x-acs-version", VERSION); + headers.put("x-acs-action", apiName); + headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date())); + headers.put("x-acs-signature-nonce", IdUtil.randomUUID()); + + // 2.2 构建签名 Header + StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") + || entry.getKey().equalsIgnoreCase("host") + || entry.getKey().equalsIgnoreCase("content-type")) + .sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + canonicalHeaders.append(lowerKey).append(":").append(String.valueOf(entry.getValue()).trim()).append("\n"); + signedHeadersBuilder.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1); + + // 3. 请求 Body + String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。 + String hashedRequestBody = DigestUtil.sha256Hex(requestBody); + + // 4. 构建 Authorization 签名 + String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest); + + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 5. 发起请求 + String urlWithParams = URL + "?" + queryString; + + System.out.println("urlWithParams ======" + urlWithParams); + try (HttpResponse response = HttpRequest.post(urlWithParams).addHeaders(headers).body(requestBody).execute()) { + return JSONUtil.parseObj(response.body()); + } + } /** - * 对指定的字符串进行URL编码。 - * 使用UTF-8编码字符集对字符串进行编码,并对特定的字符进行替换,以符合URL编码规范。 + * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范 * - * @param str 需要进行URL编码的字符串。 - * @return 编码后的字符串。其中,加号"+"被替换为"%20",星号"*"被替换为"%2A",波浪号"%7E"被替换为"~"。 + * @param str 需要进行URL编码的字符串 + * @return 编码后的字符串 */ public static String percentCode(String str) { if (str == null) { @@ -215,135 +201,4 @@ public class AliyunSmsClient extends AbstractSmsClient { } } - private SmsResponse getSmsSendResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); - smsResponse.setData(resJson); -// smsResponse.setConfigId(getConfigId()); - return smsResponse; - } - - private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { - - QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); - - smsTemplateResponse.setRequestId(resJson.getStr("RequestId")); - smsTemplateResponse.setTemplateContent(resJson.getStr("TemplateContent")); - smsTemplateResponse.setReason(resJson.getStr("Reason")); - smsTemplateResponse.setTemplateStatus(resJson.getInt("TemplateStatus")); - - return smsTemplateResponse; - } - - /** - *

类名: SmsResponse - *

说明: 发送短信返回信息 - * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class SmsResponse { - - /** - * 是否成功 - */ - private boolean success; - - /** - * 厂商原返回体 - */ - private Object data; - - /** - * 配置标识名 如未配置取对应渠道名例如 Alibaba - */ - private String configId; - } - - - /** - *

类名: QuerySmsTemplateResponse - *

说明: sms模板查询返回信息 - * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class QuerySmsTemplateResponse { - private String requestId; - private String code; - private String message; - private Integer templateStatus; - private String reason; - private String templateCode; - private Integer templateType; - private String templateName; - private String templateContent; - private String createDate; - } - - /** - * 短信接收状态 - * - * 参见 文档 - * - * @author 润普源码 - */ - @Data - public static class SmsReceiveStatus { - - /** - * 手机号 - */ - @JsonProperty("phone_number") - private String phoneNumber; - /** - * 发送时间 - */ - @JsonProperty("send_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime sendTime; - /** - * 状态报告时间 - */ - @JsonProperty("report_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime reportTime; - /** - * 是否接收成功 - */ - private Boolean success; - /** - * 状态报告说明 - */ - @JsonProperty("err_msg") - private String errMsg; - /** - * 状态报告编码 - */ - @JsonProperty("err_code") - private String errCode; - /** - * 发送序列号 - */ - @JsonProperty("biz_id") - private String bizId; - /** - * 用户序列号 - * - * 这里我们传递的是 SysSmsLogDO 的日志编号 - */ - @JsonProperty("out_id") - private String outId; - /** - * 短信长度,例如说 1、2、3 - * - * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送 - */ - @JsonProperty("sms_size") - private Integer smsSize; - - } - -} +} \ No newline at end of file From 0c8ee55a172a97aa99847c36ec1809ea1e2110c0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 9 Aug 2024 00:28:20 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E5=8E=BB=E9=99=A4=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E4=BA=91=E7=9F=AD=E4=BF=A1=E7=9A=84=20maven=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/AliyunSmsClient.java | 28 +- .../core/client/impl/AliyunSmsClient_old.java | 349 ------------------ 2 files changed, 12 insertions(+), 365 deletions(-) delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index d45f3051c..7f5096e4e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -4,7 +4,6 @@ import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.http.HttpRequest; @@ -21,10 +20,11 @@ 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.google.common.annotations.VisibleForTesting; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; @@ -176,10 +176,8 @@ public class AliyunSmsClient extends AbstractSmsClient { + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); // 5. 发起请求 - String urlWithParams = URL + "?" + queryString; - - System.out.println("urlWithParams ======" + urlWithParams); - try (HttpResponse response = HttpRequest.post(urlWithParams).addHeaders(headers).body(requestBody).execute()) { + try (HttpResponse response = HttpRequest.post(URL + "?" + queryString) + .addHeaders(headers).body(requestBody).execute()) { return JSONUtil.parseObj(response.body()); } } @@ -187,18 +185,16 @@ public class AliyunSmsClient extends AbstractSmsClient { /** * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范 * - * @param str 需要进行URL编码的字符串 + * @param str 需要进行 URL 编码的字符串 * @return 编码后的字符串 */ - public static String percentCode(String str) { - if (str == null) { - throw new IllegalArgumentException("输入字符串不可为null"); - } - try { - return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8编码不被支持", e); - } + @SneakyThrows + private static String percentCode(String str) { + Assert.notNull(str, "str 不能为空"); + return URLEncoder.encode(str, StandardCharsets.UTF_8.name()) + .replace("+", "%20") // 加号 "+" 被替换为 "%20" + .replace("*", "%2A") // 星号 "*" 被替换为 "%2A" + .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~" } } \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java deleted file mode 100644 index 37edc284b..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient_old.java +++ /dev/null @@ -1,349 +0,0 @@ -package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.digest.DigestUtil; -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.MapUtils; -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; -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; -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 com.google.common.annotations.VisibleForTesting; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.util.*; - -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; - -/** - * 阿里短信客户端的实现类 - * - * @author zzf - * @since 2021/1/25 14:17 - */ -@Slf4j -public class AliyunSmsClient_old extends AbstractSmsClient { - - public AliyunSmsClient_old(SmsChannelProperties properties) { - super(properties); - Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); - Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); - } - - @Override - protected void doInit() { -// IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); -// client = new DefaultAcsClient(profile); - } - - @Override - public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, - List> templateParams) throws Throwable { - - TreeMap queryParam = new TreeMap<>(); - queryParam.put("PhoneNumbers",mobile); - queryParam.put("SignName",properties.getSignature()); - queryParam.put("TemplateCode",apiTemplateId); - queryParam.put("TemplateParam",JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); - - JSONObject response = sendSmsRequest(queryParam,"sendSms"); - SmsResponse smsResponse = getSmsSendResponse(response); - - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); - } - - JSONObject sendSmsRequest(TreeMap queryParam,String apiName) throws IOException, URISyntaxException { - - // ************* 步骤 1:拼接规范请求串 ************* - String url = "https://dysmsapi.aliyuncs.com"; //APP接入地址+接口访问URI - String httpMethod = "POST"; // 请求方式 - String canonicalUri = "/"; - // 请求参数,当请求的查询字符串为空时,使用空字符串作为规范化查询字符串 - StringBuilder canonicalQueryString = new StringBuilder(); - queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> { - // 如果canonicalQueryString已经不是空的,则在查询参数前添加"&" - if (!canonicalQueryString.isEmpty()) { - canonicalQueryString.append("&"); - } - canonicalQueryString.append(queryPart); - System.out.println("canonicalQueryString=========>\n" + canonicalQueryString); - }); - - SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); - String SdfTime = SDF.format(new Date()); - String randomUUID = UUID.randomUUID().toString(); - - TreeMap headers = new TreeMap<>(); - headers.put("host", "dysmsapi.aliyuncs.com"); - headers.put("x-acs-action", apiName); - headers.put("x-acs-version", "2017-05-25"); - headers.put("x-acs-date", SdfTime); - headers.put("x-acs-signature-nonce", randomUUID); -// headers.put("content-type", "application/json;charset=utf-8"); - - // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 - StringBuilder canonicalHeaders = new StringBuilder(); - // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 - StringBuilder signedHeadersSb = new StringBuilder(); - headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || entry.getKey().equalsIgnoreCase("host") || entry.getKey().equalsIgnoreCase("content-type")).sorted(Map.Entry.comparingByKey()).forEach(entry -> { - String lowerKey = entry.getKey().toLowerCase(); - String value = String.valueOf(entry.getValue()).trim(); - canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); - signedHeadersSb.append(lowerKey).append(";"); - }); - String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1); - - String body = "";//短信API为RPC接口,query parameters在uri中拼接,因此request body如果没有特殊要求,设置为空。 - String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); - - - String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; - System.out.println("canonicalRequest=========>\n" + canonicalRequest); - - // ************* 步骤 2:拼接待签名字符串 ************* - String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); - String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); - - // ************* 步骤 4:拼接 Authorization ************* - String authorization = "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + ", " - + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; - headers.put("Authorization", authorization); - - // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* -// url = url + canonicalUri; - String urlWithParams = url + "?" + URLUtil.buildQuery(queryParam, null); - - HttpResponse response = HttpRequest.post(urlWithParams) - .addHeaders(headers) - .body(body) - .execute(); -// URIBuilder uriBuilder = new URIBuilder(url); -// // 添加请求参数 -// for (Map.Entry entry : queryParam.entrySet()) { -// uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); -// } -// HttpUriRequest httpRequest = new HttpPost(uriBuilder.build()); -//// HttpPost httpPost = new HttpPost(uriBuilder.build()); -//// httpRequest = httpPost; -// -// // 添加http请求头 -// for (Map.Entry entry : headers.entrySet()) { -// httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue())); -// } -// -// // 发送请求 -// CloseableHttpClient httpClient = HttpClients.createDefault(); -// CloseableHttpResponse response = httpClient.execute(httpRequest); - System.out.println("getEntity====="+response.body()); - System.out.println("response====="+response); - - return JSONUtil.parseObj(response.body()); - } - - @Override - public List parseSmsReceiveStatus(String text) { - List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess()) - .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()) - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime()) - .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()))); - } - - @Override - public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - - TreeMap queryParam = new TreeMap<>(); - queryParam.put("TemplateCode",apiTemplateId); - - JSONObject response = sendSmsRequest(queryParam,"QuerySmsTemplate"); - QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(response); - return new SmsTemplateRespDTO().setId(smsTemplateResponse.getTemplateCode()).setContent(smsTemplateResponse.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(smsTemplateResponse.getTemplateStatus())).setAuditReason(smsTemplateResponse.getReason()); - - } - - @VisibleForTesting - Integer convertSmsTemplateAuditStatus(Integer templateStatus) { - switch (templateStatus) { - case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); - case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); - case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus(); - default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus)); - } - } - - - /** - * 对指定的字符串进行URL编码。 - * 使用UTF-8编码字符集对字符串进行编码,并对特定的字符进行替换,以符合URL编码规范。 - * - * @param str 需要进行URL编码的字符串。 - * @return 编码后的字符串。其中,加号"+"被替换为"%20",星号"*"被替换为"%2A",波浪号"%7E"被替换为"~"。 - */ - public static String percentCode(String str) { - if (str == null) { - throw new IllegalArgumentException("输入字符串不可为null"); - } - try { - return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8编码不被支持", e); - } - } - - private SmsResponse getSmsSendResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); - smsResponse.setData(resJson); -// smsResponse.setConfigId(getConfigId()); - return smsResponse; - } - - private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) { - - QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse(); - - smsTemplateResponse.setRequestId(resJson.getStr("RequestId")); - smsTemplateResponse.setTemplateContent(resJson.getStr("TemplateContent")); - smsTemplateResponse.setReason(resJson.getStr("Reason")); - smsTemplateResponse.setTemplateStatus(resJson.getInt("TemplateStatus")); - - return smsTemplateResponse; - } - - /** - *

类名: SmsResponse - *

说明: 发送短信返回信息 - * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class SmsResponse { - - /** - * 是否成功 - */ - private boolean success; - - /** - * 厂商原返回体 - */ - private Object data; - - /** - * 配置标识名 如未配置取对应渠道名例如 Alibaba - */ - private String configId; - } - - - /** - *

类名: QuerySmsTemplateResponse - *

说明: sms模板查询返回信息 - * - * @author :scholar - * 2024/07/17 0:25 - **/ - @Data - public static class QuerySmsTemplateResponse { - private String requestId; - private String code; - private String message; - private Integer templateStatus; - private String reason; - private String templateCode; - private Integer templateType; - private String templateName; - private String templateContent; - private String createDate; - } - - /** - * 短信接收状态 - * - * 参见 文档 - * - * @author 润普源码 - */ - @Data - public static class SmsReceiveStatus { - - /** - * 手机号 - */ - @JsonProperty("phone_number") - private String phoneNumber; - /** - * 发送时间 - */ - @JsonProperty("send_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime sendTime; - /** - * 状态报告时间 - */ - @JsonProperty("report_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime reportTime; - /** - * 是否接收成功 - */ - private Boolean success; - /** - * 状态报告说明 - */ - @JsonProperty("err_msg") - private String errMsg; - /** - * 状态报告编码 - */ - @JsonProperty("err_code") - private String errCode; - /** - * 发送序列号 - */ - @JsonProperty("biz_id") - private String bizId; - /** - * 用户序列号 - * - * 这里我们传递的是 SysSmsLogDO 的日志编号 - */ - @JsonProperty("out_id") - private String outId; - /** - * 短信长度,例如说 1、2、3 - * - * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送 - */ - @JsonProperty("sms_size") - private Integer smsSize; - - } - -} From fd42e1711d37f9127e94ab9bec34d758bd8e8af4 Mon Sep 17 00:00:00 2001 From: brook Date: Fri, 9 Aug 2024 11:24:48 +0800 Subject: [PATCH 13/14] =?UTF-8?q?fix:=20insertOrUpdate=E6=AD=BB=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/framework/mybatis/core/mapper/BaseMapperX.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index 3f1cb3e2b..99a6c5147 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -185,10 +185,6 @@ public interface BaseMapperX extends MPJBaseMapper { return Db.updateBatchById(entities, size); } - default boolean insertOrUpdate(T entity) { - return Db.saveOrUpdate(entity); - } - default Boolean insertOrUpdateBatch(Collection collection) { return Db.saveOrUpdateBatch(collection); } From 22686bafa29a9628ef4dbe54c7d1acd11055a714 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 9 Aug 2024 20:41:35 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91SYSTEM=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=96=B0?= =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=20SmsClient=20=E7=9A=84=E5=8D=95?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/http/HttpUtils.java | 20 +++ .../sms/core/client/impl/AliyunSmsClient.java | 15 +- .../core/client/impl/AliyunSmsClientTest.java | 157 +++++++++--------- .../sms/core/client/impl/SmsClientTests.java | 5 +- 4 files changed, 106 insertions(+), 91 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 9a39a7a4e..9da4f87b1 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -122,5 +124,23 @@ public class HttpUtils { return null; } + /** + * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 + * + * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 + * + * @param url URL + * @param headers 请求头 + * @param requestBody 请求体 + * @return 请求结果 + */ + public static String post(String url, Map headers, String requestBody) { + try (HttpResponse response = HttpRequest.post(url) + .addHeaders(headers) + .body(requestBody) + .execute()) { + return response.body(); + } + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 7f5096e4e..ed6dd7a8d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -6,13 +6,12 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONArray; 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.MapUtils; +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; @@ -58,10 +57,11 @@ public class AliyunSmsClient extends AbstractSmsClient { @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { + Assert.notBlank(properties.getSignature(), "短信签名不能为空"); // 1. 执行请求 // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms TreeMap queryParam = new TreeMap<>(); - queryParam.put("PhoneNumbers",mobile); + queryParam.put("PhoneNumbers", mobile); queryParam.put("SignName", properties.getSignature()); queryParam.put("TemplateCode", apiTemplateId); queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); @@ -111,7 +111,8 @@ public class AliyunSmsClient extends AbstractSmsClient { return null; } // 2.2 请求成功 - return new SmsTemplateRespDTO().setId(apiTemplateId) + return new SmsTemplateRespDTO() + .setId(response.getStr("TemplateCode")) .setContent(response.getStr("TemplateContent")) .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) .setAuditReason(response.getStr("Reason")); @@ -176,10 +177,8 @@ public class AliyunSmsClient extends AbstractSmsClient { + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); // 5. 发起请求 - try (HttpResponse response = HttpRequest.post(URL + "?" + queryString) - .addHeaders(headers).body(requestBody).execute()) { - return JSONUtil.parseObj(response.body()); - } + String responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody); + return JSONUtil.parseObj(responseBody); } /** diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java index bc5a47140..c6e015d81 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java @@ -1,21 +1,30 @@ 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.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; 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.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; -// TODO 芋艿:需要优化 /** - * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient_old} 的单元测试 + * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试 * * @author 芋道源码 */ @@ -38,64 +47,54 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { smsClient.doInit(); } -// @Test -// public void tesSendSms_success() throws Throwable { -// // 准备参数 -// Long sendLogId = randomLongId(); -// String mobile = randomString(); -// String apiTemplateId = randomString(); -// List> templateParams = Lists.newArrayList( -// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); -// // mock 方法 -// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK")); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(mobile, acsRequest.getPhoneNumbers()); -// assertEquals(properties.getSignature(), acsRequest.getSignName()); -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); -// assertEquals(sendLogId.toString(), acsRequest.getOutId()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, -// apiTemplateId, templateParams); -// // 断言 -// assertTrue(result.getSuccess()); -// assertEquals(response.getRequestId(), result.getApiRequestId()); -// assertEquals(response.getCode(), result.getApiCode()); -// assertEquals(response.getMessage(), result.getApiMsg()); -// assertEquals(response.getBizId(), result.getSerialNo()); -// } + @Test + public void tesSendSms_success() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"OK\",\"RequestId\":\"30067CE9-3710-5984-8881-909B21D8DB28\",\"Code\":\"OK\",\"BizId\":\"800025323183427988\"}"); -// @Test -// public void tesSendSms_fail() throws Throwable { -// // 准备参数 -// Long sendLogId = randomLongId(); -// String mobile = randomString(); -// String apiTemplateId = randomString(); -// List> templateParams = Lists.newArrayList( -// new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); -// // mock 方法 -// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR")); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(mobile, acsRequest.getPhoneNumbers()); -// assertEquals(properties.getSignature(), acsRequest.getSignName()); -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); -// assertEquals(sendLogId.toString(), acsRequest.getOutId()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); -// // 断言 -// assertFalse(result.getSuccess()); -// assertEquals(response.getRequestId(), result.getApiRequestId()); -// assertEquals(response.getCode(), result.getApiCode()); -// assertEquals(response.getMessage(), result.getApiMsg()); -// assertEquals(response.getBizId(), result.getSerialNo()); -// } + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("30067CE9-3710-5984-8881-909B21D8DB28", result.getApiRequestId()); + assertEquals("OK", result.getApiCode()); + assertEquals("OK", result.getApiMsg()); + assertEquals("800025323183427988", result.getSerialNo()); + } + } + + @Test + public void tesSendSms_fail() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"手机号码格式错误\",\"RequestId\":\"B7700B8E-227E-5886-9564-26036172F01F\",\"Code\":\"isv.MOBILE_NUMBER_ILLEGAL\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("B7700B8E-227E-5886-9564-26036172F01F", result.getApiRequestId()); + assertEquals("isv.MOBILE_NUMBER_ILLEGAL", result.getApiCode()); + assertEquals("手机号码格式错误", result.getApiMsg()); + assertNull(result.getSerialNo()); + } + } @Test public void testParseSmsReceiveStatus() { @@ -129,28 +128,24 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { assertEquals(67890L, statuses.get(0).getLogId()); } -// @Test -// public void testGetSmsTemplate() throws Throwable { -// // 准备参数 -// String apiTemplateId = randomString(); -// // mock 方法 -// QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> { -// o.setCode("OK"); -// o.setTemplateStatus(1); // 设置模板通过 -// }); -// when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { -// assertEquals(apiTemplateId, acsRequest.getTemplateCode()); -// return true; -// }))).thenReturn(response); -// -// // 调用 -// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); -// // 断言 -// assertEquals(response.getTemplateCode(), result.getId()); -// assertEquals(response.getTemplateContent(), result.getContent()); -// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); -// assertEquals(response.getReason(), result.getAuditReason()); -// } + @Test + public void testGetSmsTemplate() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = randomString(); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"TemplateCode\":\"SMS_207945135\",\"RequestId\":\"6F4CC077-29C8-5BA5-AB62-5FF95068A5AC\",\"Message\":\"OK\",\"TemplateContent\":\"您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!\",\"TemplateName\":\"公告通知\",\"TemplateType\":0,\"Code\":\"OK\",\"CreateDate\":\"2020-12-23 17:34:42\",\"Reason\":\"无审批备注\",\"TemplateStatus\":1}"); + + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("SMS_207945135", result.getId()); + assertEquals("您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("无审批备注", result.getAuditReason()); + } + } @Test public void testConvertSmsTemplateAuditStatus() { diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java index 13848d33c..a5f31b4a2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -57,11 +57,12 @@ public class SmsClientTests { public void testAliyunSmsClient_sendSms() throws Throwable { SmsChannelProperties properties = new SmsChannelProperties() .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR") - .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz"); + .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz") + .setSignature("Ballcat"); AliyunSmsClient client = new AliyunSmsClient(properties); // 准备参数 Long sendLogId = System.currentTimeMillis(); - String mobile = "17321315478"; + String mobile = "173213154791"; String apiTemplateId = "SMS_207945135"; // 调用 SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));