Commit 8da08072422404b1ee66781102beda41f51701e5
1 parent
af5be1bc
【代码评审】SYSTEM:华为云 client 的 review
Showing
5 changed files
with
76 additions
and
588 deletions
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
| @@ -46,7 +46,6 @@ public class SmsCallbackController { | @@ -46,7 +46,6 @@ public class SmsCallbackController { | ||
| 46 | @PostMapping("/huawei") | 46 | @PostMapping("/huawei") |
| 47 | @PermitAll | 47 | @PermitAll |
| 48 | @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") | 48 | @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") |
| 49 | - @OperateLog(enable = false) | ||
| 50 | public CommonResult<Boolean> receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable { | 49 | public CommonResult<Boolean> receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable { |
| 51 | String text = ServletUtils.getBody(request); | 50 | String text = ServletUtils.getBody(request); |
| 52 | smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); | 51 | smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
| 1 | package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; | 1 | package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; |
| 2 | 2 | ||
| 3 | - | ||
| 4 | import cn.hutool.core.lang.Assert; | 3 | import cn.hutool.core.lang.Assert; |
| 5 | import cn.hutool.core.util.HexUtil; | 4 | import cn.hutool.core.util.HexUtil; |
| 6 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 7 | - | ||
| 8 | import cn.hutool.crypto.SecureUtil; | 6 | import cn.hutool.crypto.SecureUtil; |
| 9 | import cn.hutool.crypto.digest.DigestUtil; | 7 | import cn.hutool.crypto.digest.DigestUtil; |
| 10 | import cn.hutool.json.JSONArray; | 8 | import cn.hutool.json.JSONArray; |
| @@ -15,34 +13,27 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD | @@ -15,34 +13,27 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD | ||
| 15 | import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; | 13 | import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; |
| 16 | import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; | 14 | import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; |
| 17 | import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; | 15 | import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; |
| 18 | -import org.apache.http.client.methods.*; | ||
| 19 | -import org.apache.http.entity.StringEntity; | ||
| 20 | -import org.apache.http.impl.client.CloseableHttpClient; | ||
| 21 | -import org.apache.http.impl.client.HttpClientBuilder; | ||
| 22 | -import org.slf4j.Logger; | ||
| 23 | -import org.slf4j.LoggerFactory; | ||
| 24 | - | ||
| 25 | import com.fasterxml.jackson.annotation.JsonFormat; | 16 | import com.fasterxml.jackson.annotation.JsonFormat; |
| 26 | import com.fasterxml.jackson.annotation.JsonProperty; | 17 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 27 | import lombok.Data; | 18 | import lombok.Data; |
| 28 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
| 29 | import org.apache.http.HttpResponse; | 20 | import org.apache.http.HttpResponse; |
| 21 | +import org.apache.http.client.methods.HttpUriRequest; | ||
| 22 | +import org.apache.http.client.methods.RequestBuilder; | ||
| 23 | +import org.apache.http.entity.StringEntity; | ||
| 24 | +import org.apache.http.impl.client.CloseableHttpClient; | ||
| 25 | +import org.apache.http.impl.client.HttpClientBuilder; | ||
| 30 | 26 | ||
| 31 | -import java.io.UnsupportedEncodingException; | ||
| 32 | import java.net.URLEncoder; | 27 | import java.net.URLEncoder; |
| 33 | import java.nio.charset.StandardCharsets; | 28 | import java.nio.charset.StandardCharsets; |
| 34 | import java.text.SimpleDateFormat; | 29 | import java.text.SimpleDateFormat; |
| 35 | -import java.util.*; | ||
| 36 | - | ||
| 37 | - | ||
| 38 | import java.time.LocalDateTime; | 30 | import java.time.LocalDateTime; |
| 39 | - | 31 | +import java.util.*; |
| 40 | 32 | ||
| 41 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 33 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 42 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | 34 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| 43 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; | 35 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; |
| 44 | 36 | ||
| 45 | - | ||
| 46 | /** | 37 | /** |
| 47 | * 华为短信客户端的实现类 | 38 | * 华为短信客户端的实现类 |
| 48 | * | 39 | * |
| @@ -56,56 +47,63 @@ public class HuaweiSmsClient extends AbstractSmsClient { | @@ -56,56 +47,63 @@ public class HuaweiSmsClient extends AbstractSmsClient { | ||
| 56 | * 调用成功 code | 47 | * 调用成功 code |
| 57 | */ | 48 | */ |
| 58 | public static final String API_CODE_SUCCESS = "OK"; | 49 | public static final String API_CODE_SUCCESS = "OK"; |
| 59 | - private static final Logger LOGGER = LoggerFactory.getLogger(HuaweiSmsClient.class); | ||
| 60 | 50 | ||
| 61 | public HuaweiSmsClient(SmsChannelProperties properties) { | 51 | public HuaweiSmsClient(SmsChannelProperties properties) { |
| 62 | super(properties); | 52 | super(properties); |
| 63 | Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); | 53 | Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); |
| 64 | Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); | 54 | Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); |
| 65 | } | 55 | } |
| 56 | + | ||
| 66 | @Override | 57 | @Override |
| 67 | protected void doInit() { | 58 | protected void doInit() { |
| 68 | - | ||
| 69 | } | 59 | } |
| 60 | + | ||
| 70 | @Override | 61 | @Override |
| 71 | public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, | 62 | public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, |
| 72 | List<KeyValue<String, Object>> templateParams) throws Throwable { | 63 | List<KeyValue<String, Object>> templateParams) throws Throwable { |
| 64 | + // TODO @scholar:https://smsapi.cn-north-4.myhuaweicloud.com:443 是不是枚举成静态变量 | ||
| 73 | String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI | 65 | String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI |
| 74 | // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 | 66 | // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 |
| 75 | // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 | 67 | // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 |
| 68 | + // TODO @scholar:暂时只考虑中国大陆,所以不需要 sender 哈 | ||
| 76 | String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 | 69 | String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 |
| 77 | String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID | 70 | String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID |
| 78 | 71 | ||
| 79 | - //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔 | ||
| 80 | - String receiver = mobile; //短信接收人号码 | ||
| 81 | - | ||
| 82 | - //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 | 72 | + // 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 |
| 83 | String statusCallBack = properties.getCallbackUrl(); | 73 | String statusCallBack = properties.getCallbackUrl(); |
| 84 | 74 | ||
| 75 | + // TODO @scholar:1)是不是用 LocalDateTimeUtil.format();这样 3 行变成一行 | ||
| 76 | + // TODO @scholar:singerDate 叫 sdkDate 会更合适哈,这样理解起来简单。另外,singer 应该是 signed 么? | ||
| 85 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); | 77 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); |
| 86 | sdf.setTimeZone(TimeZone.getTimeZone("UTC")); | 78 | sdf.setTimeZone(TimeZone.getTimeZone("UTC")); |
| 87 | String singerDate = sdf.format(new Date()); | 79 | String singerDate = sdf.format(new Date()); |
| 88 | 80 | ||
| 81 | + // TODO @scholar:整个处理加密的过程,是不是应该抽成一个 private 方法哈。这样整个调用的主干更清晰。 | ||
| 89 | // ************* 步骤 1:拼接规范请求串 ************* | 82 | // ************* 步骤 1:拼接规范请求串 ************* |
| 90 | String httpRequestMethod = "POST"; | 83 | String httpRequestMethod = "POST"; |
| 91 | String canonicalUri = "/sms/batchSendSms/v1/"; | 84 | String canonicalUri = "/sms/batchSendSms/v1/"; |
| 92 | - String canonicalQueryString = "";//查询参数为空 | 85 | + String canonicalQueryString = ""; // 查询参数为空 |
| 93 | String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" | 86 | String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" |
| 94 | + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n" | 87 | + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n" |
| 95 | + "x-sdk-date:" + singerDate + "\n"; | 88 | + "x-sdk-date:" + singerDate + "\n"; |
| 89 | + // TODO @scholar:静态枚举了 | ||
| 96 | String signedHeaders = "content-type;host;x-sdk-date"; | 90 | String signedHeaders = "content-type;host;x-sdk-date"; |
| 97 | - /** | 91 | + // TODO @scholar:下面的注释,可以考虑去掉 |
| 92 | + /* | ||
| 98 | * 选填,使用无变量模板时请赋空值 String templateParas = ""; | 93 | * 选填,使用无变量模板时请赋空值 String templateParas = ""; |
| 99 | * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" | 94 | * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" |
| 100 | * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]" | 95 | * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]" |
| 101 | */ | 96 | */ |
| 97 | + // TODO @scholar:CollectionUtils.convertList 可以把 4 行变成 1 行。 | ||
| 98 | + // TODO @scholar:templateParams 拼写错误哈 | ||
| 102 | List<String> templateParas = new ArrayList<>(); | 99 | List<String> templateParas = new ArrayList<>(); |
| 103 | for (KeyValue<String, Object> kv : templateParams) { | 100 | for (KeyValue<String, Object> kv : templateParams) { |
| 104 | templateParas.add(String.valueOf(kv.getValue())); | 101 | templateParas.add(String.valueOf(kv.getValue())); |
| 105 | } | 102 | } |
| 106 | 103 | ||
| 107 | - //请求Body,不携带签名名称时,signature请填null | ||
| 108 | - String body = buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, null); | 104 | + // 请求Body,不携带签名名称时,signature请填null |
| 105 | + String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null); | ||
| 106 | + // TODO @scholar:Assert 断言,抛出异常 | ||
| 109 | if (null == body || body.isEmpty()) { | 107 | if (null == body || body.isEmpty()) { |
| 110 | return null; | 108 | return null; |
| 111 | } | 109 | } |
| @@ -114,6 +112,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { | @@ -114,6 +112,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { | ||
| 114 | + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; | 112 | + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; |
| 115 | 113 | ||
| 116 | // ************* 步骤 2:拼接待签名字符串 ************* | 114 | // ************* 步骤 2:拼接待签名字符串 ************* |
| 115 | + // TODO @scholar:sha256Hex 是不是更简洁哈 | ||
| 117 | String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); | 116 | String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); |
| 118 | String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest; | 117 | String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest; |
| 119 | 118 | ||
| @@ -125,22 +124,26 @@ public class HuaweiSmsClient extends AbstractSmsClient { | @@ -125,22 +124,26 @@ public class HuaweiSmsClient extends AbstractSmsClient { | ||
| 125 | + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; | 124 | + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; |
| 126 | 125 | ||
| 127 | // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* | 126 | // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* |
| 127 | + // TODO @scholar:考虑了下,还是换 hutool 的 httpUtils。因为未来 httpclient 我们可能会移除掉 | ||
| 128 | HttpUriRequest postMethod = RequestBuilder.post() | 128 | HttpUriRequest postMethod = RequestBuilder.post() |
| 129 | .setUri(url) | 129 | .setUri(url) |
| 130 | .setEntity(new StringEntity(body, StandardCharsets.UTF_8)) | 130 | .setEntity(new StringEntity(body, StandardCharsets.UTF_8)) |
| 131 | .setHeader("Content-Type","application/x-www-form-urlencoded") | 131 | .setHeader("Content-Type","application/x-www-form-urlencoded") |
| 132 | - .setHeader("X-Sdk-Date",singerDate) | ||
| 133 | - .setHeader("Authorization",authorization) | 132 | + .setHeader("X-Sdk-Date", singerDate) |
| 133 | + .setHeader("Authorization", authorization) | ||
| 134 | .build(); | 134 | .build(); |
| 135 | + // TODO @scholar:这种不太适合一直 new 的哈 | ||
| 135 | CloseableHttpClient client = HttpClientBuilder.create().build(); | 136 | CloseableHttpClient client = HttpClientBuilder.create().build(); |
| 136 | HttpResponse response = client.execute(postMethod); | 137 | HttpResponse response = client.execute(postMethod); |
| 137 | - | 138 | + // TODO @scholar:失败的情况下的处理 |
| 139 | + // TODO @scholar:setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) 这部分,空一行。一行代码太多了,阅读性不太好哈 | ||
| 138 | return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) | 140 | return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) |
| 139 | .setApiRequestId(null).setApiCode(null).setApiMsg(null); | 141 | .setApiRequestId(null).setApiCode(null).setApiMsg(null); |
| 140 | } | 142 | } |
| 141 | 143 | ||
| 142 | static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas, | 144 | static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas, |
| 143 | - String statusCallBack, String signature) throws UnsupportedEncodingException { | 145 | + String statusCallBack, @SuppressWarnings("SameParameterValue") String signature) { |
| 146 | + // TODO @scholar:参数不满足,是不是抛出异常更好哈;通过 hutool 的 Assert 去断言 | ||
| 144 | if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() | 147 | if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() |
| 145 | || templateId.isEmpty()) { | 148 | || templateId.isEmpty()) { |
| 146 | System.out.println("buildRequestBody(): sender, receiver or templateId is null."); | 149 | System.out.println("buildRequestBody(): sender, receiver or templateId is null."); |
| @@ -151,17 +154,20 @@ public class HuaweiSmsClient extends AbstractSmsClient { | @@ -151,17 +154,20 @@ public class HuaweiSmsClient extends AbstractSmsClient { | ||
| 151 | appendToBody(body, "from=", sender); | 154 | appendToBody(body, "from=", sender); |
| 152 | appendToBody(body, "&to=", receiver); | 155 | appendToBody(body, "&to=", receiver); |
| 153 | appendToBody(body, "&templateId=", templateId); | 156 | appendToBody(body, "&templateId=", templateId); |
| 157 | + // TODO @scholar:new JSONArray(templateParas).toString(),是不是 JsonUtils.toString 呀? | ||
| 154 | appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString()); | 158 | appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString()); |
| 155 | appendToBody(body, "&statusCallback=", statusCallBack); | 159 | appendToBody(body, "&statusCallback=", statusCallBack); |
| 156 | appendToBody(body, "&signature=", signature); | 160 | appendToBody(body, "&signature=", signature); |
| 157 | return body.toString(); | 161 | return body.toString(); |
| 158 | } | 162 | } |
| 159 | 163 | ||
| 160 | - private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException { | 164 | + private static void appendToBody(StringBuilder body, String key, String val) { |
| 165 | + // TODO @scholar:StrUtils.isNotEmpty(val),是不是更简洁哈 | ||
| 161 | if (null != val && !val.isEmpty()) { | 166 | if (null != val && !val.isEmpty()) { |
| 162 | - body.append(key).append(URLEncoder.encode(val, "UTF-8")); | 167 | + body.append(key).append(URLEncoder.encode(val, StandardCharsets.UTF_8)); |
| 163 | } | 168 | } |
| 164 | } | 169 | } |
| 170 | + | ||
| 165 | @Override | 171 | @Override |
| 166 | public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) { | 172 | public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) { |
| 167 | List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); | 173 | List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); |
| @@ -173,10 +179,10 @@ public class HuaweiSmsClient extends AbstractSmsClient { | @@ -173,10 +179,10 @@ public class HuaweiSmsClient extends AbstractSmsClient { | ||
| 173 | 179 | ||
| 174 | @Override | 180 | @Override |
| 175 | public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { | 181 | public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { |
| 176 | - //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 | ||
| 177 | - return new SmsTemplateRespDTO().setId(null).setContent(null) | 182 | + // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现 |
| 183 | + // 对应文档 https://support.huaweicloud.com/api-msgsms/sms_05_0040.html | ||
| 184 | + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null) | ||
| 178 | .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); | 185 | .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); |
| 179 | - | ||
| 180 | } | 186 | } |
| 181 | 187 | ||
| 182 | /** | 188 | /** |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java deleted
| 1 | -package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import java.io.UnsupportedEncodingException; | ||
| 5 | -import java.net.URLDecoder; | ||
| 6 | -import java.net.URLEncoder; | ||
| 7 | -import java.util.ArrayList; | ||
| 8 | -import java.util.Hashtable; | ||
| 9 | -import java.util.Iterator; | ||
| 10 | -import java.util.List; | ||
| 11 | -import java.util.Map; | ||
| 12 | -import java.util.regex.Matcher; | ||
| 13 | -import java.util.regex.Pattern; | ||
| 14 | - | ||
| 15 | -/** | ||
| 16 | - * 华为短信待签名request | ||
| 17 | - * | ||
| 18 | - * @author scholar | ||
| 19 | - * @since 2024/6/02 11:55 | ||
| 20 | - */ | ||
| 21 | -public class HuaweiRequest { | ||
| 22 | - private String key = null; | ||
| 23 | - private String secret = null; | ||
| 24 | - private String method = null; | ||
| 25 | - private String url = null; | ||
| 26 | - private String body = null; | ||
| 27 | - private String fragment = null; | ||
| 28 | - private Map<String, String> headers = new Hashtable(); | ||
| 29 | - private Map<String, List<String>> queryString = new Hashtable(); | ||
| 30 | - private static final Pattern PATTERN = Pattern.compile("^(?i)(post|put|patch|delete|get|options|head)$"); | ||
| 31 | - | ||
| 32 | - public HuaweiRequest() { | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - /** @deprecated */ | ||
| 36 | - @Deprecated | ||
| 37 | - public String getRegion() { | ||
| 38 | - return ""; | ||
| 39 | - } | ||
| 40 | - | ||
| 41 | - /** @deprecated */ | ||
| 42 | - @Deprecated | ||
| 43 | - public String getServiceName() { | ||
| 44 | - return ""; | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - public String getKey() { | ||
| 48 | - return this.key; | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - public String getSecrect() { | ||
| 52 | - return this.secret; | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | -// public HttpMethodName getMethod() { | ||
| 56 | -// return HttpMethodName.valueOf(this.method.toUpperCase(Locale.getDefault())); | ||
| 57 | -// } | ||
| 58 | - | ||
| 59 | - public String getBody() { | ||
| 60 | - return this.body; | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - public Map<String, String> getHeaders() { | ||
| 64 | - return this.headers; | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - /** @deprecated */ | ||
| 68 | - @Deprecated | ||
| 69 | - public void setRegion(String region) { | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - /** @deprecated */ | ||
| 73 | - @Deprecated | ||
| 74 | - public void setServiceName(String serviceName) { | ||
| 75 | - } | ||
| 76 | - | ||
| 77 | - public void setAppKey(String appKey) throws RuntimeException { | ||
| 78 | - if (null != appKey && !appKey.trim().isEmpty()) { | ||
| 79 | - this.key = appKey; | ||
| 80 | - } else { | ||
| 81 | - throw new RuntimeException("appKey can not be empty"); | ||
| 82 | - } | ||
| 83 | - } | ||
| 84 | - | ||
| 85 | - public void setAppSecrect(String appSecret) throws RuntimeException { | ||
| 86 | - if (null != appSecret && !appSecret.trim().isEmpty()) { | ||
| 87 | - this.secret = appSecret; | ||
| 88 | - } else { | ||
| 89 | - throw new RuntimeException("appSecrect can not be empty"); | ||
| 90 | - } | ||
| 91 | - } | ||
| 92 | - | ||
| 93 | - public void setKey(String appKey) throws RuntimeException { | ||
| 94 | - if (null != appKey && !appKey.trim().isEmpty()) { | ||
| 95 | - this.key = appKey; | ||
| 96 | - } else { | ||
| 97 | - throw new RuntimeException("appKey can not be empty"); | ||
| 98 | - } | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - public void setSecret(String appSecret) throws RuntimeException { | ||
| 102 | - if (null != appSecret && !appSecret.trim().isEmpty()) { | ||
| 103 | - this.secret = appSecret; | ||
| 104 | - } else { | ||
| 105 | - throw new RuntimeException("appSecrect can not be empty"); | ||
| 106 | - } | ||
| 107 | - } | ||
| 108 | - | ||
| 109 | - public void setMethod(String method) throws RuntimeException { | ||
| 110 | - if (null == method) { | ||
| 111 | - throw new RuntimeException("method can not be empty"); | ||
| 112 | - } else { | ||
| 113 | - Matcher match = PATTERN.matcher(method); | ||
| 114 | - if (!match.matches()) { | ||
| 115 | - throw new RuntimeException("unsupported method"); | ||
| 116 | - } else { | ||
| 117 | - this.method = method; | ||
| 118 | - } | ||
| 119 | - } | ||
| 120 | - } | ||
| 121 | - | ||
| 122 | - public String getUrl() throws UnsupportedEncodingException { | ||
| 123 | - StringBuilder uri = new StringBuilder(); | ||
| 124 | - uri.append(this.url); | ||
| 125 | - if (this.queryString.size() > 0) { | ||
| 126 | - uri.append("?"); | ||
| 127 | - int loop = 0; | ||
| 128 | - Iterator var3 = this.queryString.entrySet().iterator(); | ||
| 129 | - | ||
| 130 | - while(var3.hasNext()) { | ||
| 131 | - Map.Entry<String, List<String>> entry = (Map.Entry)var3.next(); | ||
| 132 | - | ||
| 133 | - for(Iterator var5 = ((List)entry.getValue()).iterator(); var5.hasNext(); ++loop) { | ||
| 134 | - String value = (String)var5.next(); | ||
| 135 | - if (loop > 0) { | ||
| 136 | - uri.append("&"); | ||
| 137 | - } | ||
| 138 | - | ||
| 139 | - uri.append(URLEncoder.encode((String)entry.getKey(), "UTF-8")); | ||
| 140 | - uri.append("="); | ||
| 141 | - uri.append(URLEncoder.encode(value, "UTF-8")); | ||
| 142 | - } | ||
| 143 | - } | ||
| 144 | - } | ||
| 145 | - | ||
| 146 | - if (this.fragment != null) { | ||
| 147 | - uri.append("#"); | ||
| 148 | - uri.append(this.fragment); | ||
| 149 | - } | ||
| 150 | - | ||
| 151 | - return uri.toString(); | ||
| 152 | - } | ||
| 153 | - | ||
| 154 | - public void setUrl(String urlRet) throws RuntimeException, UnsupportedEncodingException { | ||
| 155 | - if (urlRet != null && !urlRet.trim().isEmpty()) { | ||
| 156 | - int i = urlRet.indexOf(35); | ||
| 157 | - if (i >= 0) { | ||
| 158 | - urlRet = urlRet.substring(0, i); | ||
| 159 | - } | ||
| 160 | - | ||
| 161 | - i = urlRet.indexOf(63); | ||
| 162 | - this.url = urlRet; | ||
| 163 | - if (i >= 0) { | ||
| 164 | - String query = urlRet.substring(i + 1, urlRet.length()); | ||
| 165 | - String[] var4 = query.split("&"); | ||
| 166 | - int var5 = var4.length; | ||
| 167 | - | ||
| 168 | - for(int var6 = 0; var6 < var5; ++var6) { | ||
| 169 | - String item = var4[var6]; | ||
| 170 | - String[] spl = item.split("=", 2); | ||
| 171 | - String keyRet = spl[0]; | ||
| 172 | - String value = ""; | ||
| 173 | - if (spl.length > 1) { | ||
| 174 | - value = spl[1]; | ||
| 175 | - } | ||
| 176 | - | ||
| 177 | - if (!keyRet.trim().isEmpty()) { | ||
| 178 | - keyRet = URLDecoder.decode(keyRet, "UTF-8"); | ||
| 179 | - value = URLDecoder.decode(value, "UTF-8"); | ||
| 180 | - this.addQueryStringParam(keyRet, value); | ||
| 181 | - } | ||
| 182 | - } | ||
| 183 | - | ||
| 184 | - urlRet = urlRet.substring(0, i); | ||
| 185 | - this.url = urlRet; | ||
| 186 | - } | ||
| 187 | - } else { | ||
| 188 | - throw new RuntimeException("url can not be empty"); | ||
| 189 | - } | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - public String getPath() { | ||
| 193 | - String urlRet = this.url; | ||
| 194 | - int i = urlRet.indexOf("://"); | ||
| 195 | - if (i >= 0) { | ||
| 196 | - urlRet = urlRet.substring(i + 3); | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - i = urlRet.indexOf(47); | ||
| 200 | - return i >= 0 ? urlRet.substring(i) : "/"; | ||
| 201 | - } | ||
| 202 | - | ||
| 203 | - public String getHost() { | ||
| 204 | - String urlRet = this.url; | ||
| 205 | - int i = urlRet.indexOf("://"); | ||
| 206 | - if (i >= 0) { | ||
| 207 | - urlRet = urlRet.substring(i + 3); | ||
| 208 | - } | ||
| 209 | - | ||
| 210 | - i = urlRet.indexOf(47); | ||
| 211 | - if (i >= 0) { | ||
| 212 | - urlRet = urlRet.substring(0, i); | ||
| 213 | - } | ||
| 214 | - | ||
| 215 | - return urlRet; | ||
| 216 | - } | ||
| 217 | - | ||
| 218 | - public void setBody(String body) { | ||
| 219 | - this.body = body; | ||
| 220 | - } | ||
| 221 | - | ||
| 222 | - public void addQueryStringParam(String name, String value) { | ||
| 223 | - List<String> paramList = (List)this.queryString.get(name); | ||
| 224 | - if (paramList == null) { | ||
| 225 | - paramList = new ArrayList(); | ||
| 226 | - this.queryString.put(name, paramList); | ||
| 227 | - } | ||
| 228 | - | ||
| 229 | - ((List)paramList).add(value); | ||
| 230 | - } | ||
| 231 | - | ||
| 232 | - public Map<String, List<String>> getQueryStringParams() { | ||
| 233 | - return this.queryString; | ||
| 234 | - } | ||
| 235 | - | ||
| 236 | - public String getFragment() { | ||
| 237 | - return this.fragment; | ||
| 238 | - } | ||
| 239 | - | ||
| 240 | - public void setFragment(String fragment) throws RuntimeException, UnsupportedEncodingException { | ||
| 241 | - if (fragment != null && !fragment.trim().isEmpty()) { | ||
| 242 | - this.fragment = URLEncoder.encode(fragment, "UTF-8"); | ||
| 243 | - } else { | ||
| 244 | - throw new RuntimeException("fragment can not be empty"); | ||
| 245 | - } | ||
| 246 | - } | ||
| 247 | - | ||
| 248 | - public void addHeader(String name, String value) { | ||
| 249 | - if (name != null && !name.trim().isEmpty()) { | ||
| 250 | - this.headers.put(name, value); | ||
| 251 | - } | ||
| 252 | - } | ||
| 253 | -} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java deleted
| 1 | -package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import cn.hutool.core.util.HexUtil; | ||
| 5 | -import cn.hutool.core.util.URLUtil; | ||
| 6 | -import java.io.UnsupportedEncodingException; | ||
| 7 | -import java.net.URI; | ||
| 8 | -import java.net.URISyntaxException; | ||
| 9 | -import java.nio.charset.StandardCharsets; | ||
| 10 | -import java.security.InvalidKeyException; | ||
| 11 | -import java.security.MessageDigest; | ||
| 12 | -import java.security.NoSuchAlgorithmException; | ||
| 13 | -import java.security.Security; | ||
| 14 | -import java.text.SimpleDateFormat; | ||
| 15 | -import java.util.*; | ||
| 16 | -import java.util.regex.Matcher; | ||
| 17 | -import java.util.regex.Pattern; | ||
| 18 | -import javax.crypto.Mac; | ||
| 19 | -import javax.crypto.spec.SecretKeySpec; | ||
| 20 | -import org.apache.commons.codec.binary.StringUtils; | ||
| 21 | -import org.bouncycastle.crypto.digests.SM3Digest; | ||
| 22 | -import org.openeuler.BGMJCEProvider; | ||
| 23 | - | ||
| 24 | -/** | ||
| 25 | - * 华为短信request签名实现类 | ||
| 26 | - * | ||
| 27 | - * @author scholar | ||
| 28 | - * @since 2024/6/02 11:55 | ||
| 29 | - */ | ||
| 30 | -public class HuaweiSigner { | ||
| 31 | - public static final String LINE_SEPARATOR = "\n"; | ||
| 32 | - public static final String SDK_SIGNING_ALGORITHM = "SDK-HMAC-SHA256"; | ||
| 33 | - public static final String X_SDK_CONTENT_SHA256 = "x-sdk-content-sha256"; | ||
| 34 | - public static final String X_SDK_DATE = "X-Sdk-Date"; | ||
| 35 | - public static final String AUTHORIZATION = "Authorization"; | ||
| 36 | - private static final Pattern AUTHORIZATION_PATTERN_SHA256 = Pattern.compile("SDK-HMAC-SHA256\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); | ||
| 37 | - private static final Pattern AUTHORIZATION_PATTERN_SM3 = Pattern.compile("SDK-HMAC-SM3\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); | ||
| 38 | - private static final String LINUX_NEW_LINE = "\n"; | ||
| 39 | - public static final String HOST = "Host"; | ||
| 40 | - public String messageDigestAlgorithm = "SDK-HMAC-SHA256"; | ||
| 41 | - | ||
| 42 | - public HuaweiSigner(String messageDigestAlgorithm) { | ||
| 43 | - this.messageDigestAlgorithm = messageDigestAlgorithm; | ||
| 44 | - } | ||
| 45 | - | ||
| 46 | - public HuaweiSigner() { | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - public void sign(HuaweiRequest request) throws UnsupportedEncodingException { | ||
| 50 | - String singerDate = this.getHeader(request, "X-Sdk-Date"); | ||
| 51 | - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); | ||
| 52 | - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); | ||
| 53 | - if (singerDate == null) { | ||
| 54 | - singerDate = sdf.format(new Date()); | ||
| 55 | - request.addHeader("X-Sdk-Date", singerDate); | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - this.addHostHeader(request); | ||
| 59 | - String messageDigestContent = this.calculateContentHash(request); | ||
| 60 | - String[] signedHeaders = this.getSignedHeaders(request); | ||
| 61 | - String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); | ||
| 62 | - byte[] signingKey = this.deriveSigningKey(request.getSecrect()); | ||
| 63 | - String stringToSign = this.createStringToSign(canonicalRequest, singerDate); | ||
| 64 | - byte[] signature = this.computeSignature(stringToSign, signingKey); | ||
| 65 | - String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); | ||
| 66 | - request.addHeader("Authorization", signatureResult); | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - protected String getCanonicalizedResourcePath(String resourcePath) throws UnsupportedEncodingException { | ||
| 70 | - if (resourcePath != null && !resourcePath.isEmpty()) { | ||
| 71 | - try { | ||
| 72 | - resourcePath = (new URI(resourcePath)).getPath(); | ||
| 73 | - } catch (URISyntaxException var3) { | ||
| 74 | - return resourcePath; | ||
| 75 | - } | ||
| 76 | - | ||
| 77 | - String value = URLUtil.encode(resourcePath); | ||
| 78 | - if (!value.startsWith("/")) { | ||
| 79 | - value = "/".concat(value); | ||
| 80 | - } | ||
| 81 | - | ||
| 82 | - if (!value.endsWith("/")) { | ||
| 83 | - value = value.concat("/"); | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - return value; | ||
| 87 | - } else { | ||
| 88 | - return "/"; | ||
| 89 | - } | ||
| 90 | - } | ||
| 91 | - | ||
| 92 | - protected String getCanonicalizedQueryString(Map<String, List<String>> parameters) throws UnsupportedEncodingException { | ||
| 93 | - SortedMap<String, List<String>> sorted = new TreeMap(); | ||
| 94 | - Iterator var3 = parameters.entrySet().iterator(); | ||
| 95 | - | ||
| 96 | - while(var3.hasNext()) { | ||
| 97 | - Map.Entry<String, List<String>> entry = (Map.Entry)var3.next(); | ||
| 98 | - String encodedParamName = URLUtil.encode((String) entry.getKey()); | ||
| 99 | - List<String> paramValues = (List)entry.getValue(); | ||
| 100 | - List<String> encodedValues = new ArrayList(paramValues.size()); | ||
| 101 | - Iterator var8 = paramValues.iterator(); | ||
| 102 | - | ||
| 103 | - while(var8.hasNext()) { | ||
| 104 | - String value = (String)var8.next(); | ||
| 105 | - encodedValues.add(URLUtil.encode(value)); | ||
| 106 | - } | ||
| 107 | - | ||
| 108 | - Collections.sort(encodedValues); | ||
| 109 | - sorted.put(encodedParamName, encodedValues); | ||
| 110 | - } | ||
| 111 | - | ||
| 112 | - StringBuilder result = new StringBuilder(); | ||
| 113 | - Iterator var11 = sorted.entrySet().iterator(); | ||
| 114 | - | ||
| 115 | - while(var11.hasNext()) { | ||
| 116 | - Map.Entry<String, List<String>> entry = (Map.Entry)var11.next(); | ||
| 117 | - | ||
| 118 | - String value; | ||
| 119 | - for(Iterator var13 = ((List)entry.getValue()).iterator(); var13.hasNext(); result.append((String)entry.getKey()).append("=").append(value)) { | ||
| 120 | - value = (String)var13.next(); | ||
| 121 | - if (result.length() > 0) { | ||
| 122 | - result.append("&"); | ||
| 123 | - } | ||
| 124 | - } | ||
| 125 | - } | ||
| 126 | - | ||
| 127 | - return result.toString(); | ||
| 128 | - } | ||
| 129 | - | ||
| 130 | - protected String createCanonicalRequest(HuaweiRequest request, String[] signedHeaders, String messageDigestContent) throws UnsupportedEncodingException { | ||
| 131 | - return "POST" + "\n" + this.getCanonicalizedResourcePath(request.getPath()) + "\n" + this.getCanonicalizedQueryString(request.getQueryStringParams()) + "\n" + this.getCanonicalizedHeaderString(request, signedHeaders) + "\n" + this.getSignedHeadersString(signedHeaders) + "\n" + messageDigestContent; | ||
| 132 | - } | ||
| 133 | - | ||
| 134 | - protected String createStringToSign(String canonicalRequest, String singerDate) { | ||
| 135 | - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hash(canonicalRequest)) : this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hashSm3(canonicalRequest)); | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - private byte[] deriveSigningKey(String secret) { | ||
| 139 | - return secret.getBytes(StandardCharsets.UTF_8); | ||
| 140 | - } | ||
| 141 | - | ||
| 142 | - protected byte[] sign(byte[] data, byte[] key, String algorithm) { | ||
| 143 | - try { | ||
| 144 | - if (Objects.equals(algorithm, "HmacSM3")) { | ||
| 145 | - Security.insertProviderAt(new BGMJCEProvider(), 1); | ||
| 146 | - } | ||
| 147 | - | ||
| 148 | - Mac mac = Mac.getInstance(algorithm); | ||
| 149 | - mac.init(new SecretKeySpec(key, algorithm)); | ||
| 150 | - return mac.doFinal(data); | ||
| 151 | - } catch (InvalidKeyException | NoSuchAlgorithmException var5) { | ||
| 152 | - return new byte[0]; | ||
| 153 | - } | ||
| 154 | - } | ||
| 155 | - | ||
| 156 | - protected final byte[] computeSignature(String stringToSign, byte[] signingKey) { | ||
| 157 | - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSHA256") : this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSM3"); | ||
| 158 | - } | ||
| 159 | - | ||
| 160 | - private String buildAuthorizationHeader(String[] signedHeaders, byte[] signature, String accessKey) { | ||
| 161 | - String credential = "Access=" + accessKey; | ||
| 162 | - String signerHeaders = "SignedHeaders=" + this.getSignedHeadersString(signedHeaders); | ||
| 163 | - String signatureHeader = "Signature=" + HexUtil.encodeHexStr(signature); | ||
| 164 | - return this.messageDigestAlgorithm + " " + credential + ", " + signerHeaders + ", " + signatureHeader; | ||
| 165 | - } | ||
| 166 | - | ||
| 167 | - protected String[] getSignedHeaders(HuaweiRequest request) { | ||
| 168 | - String[] signedHeaders = (String[])request.getHeaders().keySet().toArray(new String[0]); | ||
| 169 | - Arrays.sort(signedHeaders, String.CASE_INSENSITIVE_ORDER); | ||
| 170 | - return signedHeaders; | ||
| 171 | - } | ||
| 172 | - | ||
| 173 | - protected String getCanonicalizedHeaderString(HuaweiRequest request, String[] signedHeaders) { | ||
| 174 | - Map<String, String> requestHeaders = request.getHeaders(); | ||
| 175 | - StringBuilder buffer = new StringBuilder(); | ||
| 176 | - String[] var5 = signedHeaders; | ||
| 177 | - int var6 = signedHeaders.length; | ||
| 178 | - | ||
| 179 | - for(int var7 = 0; var7 < var6; ++var7) { | ||
| 180 | - String header = var5[var7]; | ||
| 181 | - String key = header.toLowerCase(Locale.getDefault()); | ||
| 182 | - String value = (String)requestHeaders.get(header); | ||
| 183 | - buffer.append(key).append(":"); | ||
| 184 | - if (value != null) { | ||
| 185 | - buffer.append(value.trim()); | ||
| 186 | - } | ||
| 187 | - | ||
| 188 | - buffer.append("\n"); | ||
| 189 | - } | ||
| 190 | - | ||
| 191 | - return buffer.toString(); | ||
| 192 | - } | ||
| 193 | - | ||
| 194 | - protected String getSignedHeadersString(String[] signedHeaders) { | ||
| 195 | - StringBuilder buffer = new StringBuilder(); | ||
| 196 | - String[] var3 = signedHeaders; | ||
| 197 | - int var4 = signedHeaders.length; | ||
| 198 | - | ||
| 199 | - for(int var5 = 0; var5 < var4; ++var5) { | ||
| 200 | - String header = var3[var5]; | ||
| 201 | - if (buffer.length() > 0) { | ||
| 202 | - buffer.append(";"); | ||
| 203 | - } | ||
| 204 | - | ||
| 205 | - buffer.append(header.toLowerCase(Locale.getDefault())); | ||
| 206 | - } | ||
| 207 | - | ||
| 208 | - return buffer.toString(); | ||
| 209 | - } | ||
| 210 | - | ||
| 211 | - protected void addHostHeader(HuaweiRequest request) { | ||
| 212 | - boolean haveHostHeader = false; | ||
| 213 | - Iterator var3 = request.getHeaders().keySet().iterator(); | ||
| 214 | - | ||
| 215 | - while(var3.hasNext()) { | ||
| 216 | - String key = (String)var3.next(); | ||
| 217 | - if ("Host".equalsIgnoreCase(key)) { | ||
| 218 | - haveHostHeader = true; | ||
| 219 | - break; | ||
| 220 | - } | ||
| 221 | - } | ||
| 222 | - | ||
| 223 | - if (!haveHostHeader) { | ||
| 224 | - request.addHeader("Host", request.getHost()); | ||
| 225 | - } | ||
| 226 | - | ||
| 227 | - } | ||
| 228 | - | ||
| 229 | - protected String getHeader(HuaweiRequest request, String header) { | ||
| 230 | - if (header == null) { | ||
| 231 | - return null; | ||
| 232 | - } else { | ||
| 233 | - Map<String, String> headers = request.getHeaders(); | ||
| 234 | - Iterator var4 = headers.entrySet().iterator(); | ||
| 235 | - | ||
| 236 | - Map.Entry entry; | ||
| 237 | - do { | ||
| 238 | - if (!var4.hasNext()) { | ||
| 239 | - return null; | ||
| 240 | - } | ||
| 241 | - | ||
| 242 | - entry = (Map.Entry)var4.next(); | ||
| 243 | - } while(!header.equalsIgnoreCase((String)entry.getKey())); | ||
| 244 | - | ||
| 245 | - return (String)entry.getValue(); | ||
| 246 | - } | ||
| 247 | - } | ||
| 248 | - | ||
| 249 | - public boolean verify(HuaweiRequest request) throws UnsupportedEncodingException { | ||
| 250 | - String singerDate = this.getHeader(request, "X-Sdk-Date"); | ||
| 251 | - String authorization = this.getHeader(request, "Authorization"); | ||
| 252 | - Matcher match = AUTHORIZATION_PATTERN_SM3.matcher(authorization); | ||
| 253 | - if (StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256")) { | ||
| 254 | - match = AUTHORIZATION_PATTERN_SHA256.matcher(authorization); | ||
| 255 | - } | ||
| 256 | - | ||
| 257 | - if (!match.find()) { | ||
| 258 | - return false; | ||
| 259 | - } else { | ||
| 260 | - String[] signedHeaders = match.group(2).split(";"); | ||
| 261 | - byte[] signingKey = this.deriveSigningKey(request.getSecrect()); | ||
| 262 | - String messageDigestContent = this.calculateContentHash(request); | ||
| 263 | - String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); | ||
| 264 | - String stringToSign = this.createStringToSign(canonicalRequest, singerDate); | ||
| 265 | - byte[] signature = this.computeSignature(stringToSign, signingKey); | ||
| 266 | - String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); | ||
| 267 | - return signatureResult.equals(authorization); | ||
| 268 | - } | ||
| 269 | - } | ||
| 270 | - | ||
| 271 | - protected String calculateContentHash(HuaweiRequest request) { | ||
| 272 | - String content_sha256 = this.getHeader(request, "x-sdk-content-sha256"); | ||
| 273 | - if (content_sha256 != null) { | ||
| 274 | - return content_sha256; | ||
| 275 | - } else { | ||
| 276 | - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? HexUtil.encodeHexStr(this.hash(request.getBody()),true) : HexUtil.encodeHexStr(this.hashSm3(request.getBody())); | ||
| 277 | - } | ||
| 278 | - } | ||
| 279 | - | ||
| 280 | - public byte[] hash(String text) { | ||
| 281 | - try { | ||
| 282 | - MessageDigest md = MessageDigest.getInstance("SHA-256"); | ||
| 283 | - md.update(text.getBytes(StandardCharsets.UTF_8)); | ||
| 284 | - return md.digest(); | ||
| 285 | - } catch (NoSuchAlgorithmException var3) { | ||
| 286 | - return new byte[0]; | ||
| 287 | - } | ||
| 288 | - } | ||
| 289 | - | ||
| 290 | - public byte[] hashSm3(String text) { | ||
| 291 | - byte[] srcData = text.getBytes(StandardCharsets.UTF_8); | ||
| 292 | - SM3Digest digest = new SM3Digest(); | ||
| 293 | - digest.update(srcData, 0, srcData.length); | ||
| 294 | - byte[] hash = new byte[digest.getDigestSize()]; | ||
| 295 | - digest.doFinal(hash, 0); | ||
| 296 | - return hash; | ||
| 297 | - } | ||
| 298 | - | ||
| 299 | - | ||
| 300 | -} |
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.KeyValue; | ||
| 4 | +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; | ||
| 5 | +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; | ||
| 6 | +import org.junit.jupiter.api.Disabled; | ||
| 7 | +import org.junit.jupiter.api.Test; | ||
| 8 | + | ||
| 9 | +import java.util.List; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 各种 {@link SmsClientTests 集成测试 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +public class SmsClientTests { | ||
| 17 | + | ||
| 18 | + @Test | ||
| 19 | + @Disabled | ||
| 20 | + public void testHuaweiSmsClient() throws Throwable { | ||
| 21 | + SmsChannelProperties properties = new SmsChannelProperties() | ||
| 22 | + .setApiKey("123") | ||
| 23 | + .setApiSecret("456"); | ||
| 24 | + HuaweiSmsClient client = new HuaweiSmsClient(properties); | ||
| 25 | + // 准备参数 | ||
| 26 | + Long sendLogId = System.currentTimeMillis(); | ||
| 27 | + String mobile = "15601691323"; | ||
| 28 | + String apiTemplateId = "xx test01"; | ||
| 29 | + List<KeyValue<String, Object>> templateParams = List.of(new KeyValue<>("code", "1024")); | ||
| 30 | + // 调用 | ||
| 31 | + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); | ||
| 32 | + // 打印结果 | ||
| 33 | + System.out.println(smsSendRespDTO); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | +} |