mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-12 21:02:08 +08:00
cf-framework:sms短信client移除云厂商sdk,直接对接http方式
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
package com.cf.imes.framework.common.core;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key Value 的键值对
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class KeyValue<K, V> implements Serializable {
|
||||||
|
|
||||||
|
private K key;
|
||||||
|
private V value;
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
package com.cf.imes.framework.common.util.collection;
|
package com.cf.imes.framework.common.util.collection;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public class MapUtils {
|
|||||||
consumer.accept(value);
|
consumer.accept(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <K, V> Map<K, V> convertMap(List<Pair<K, V>> keyValues) {
|
public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
|
||||||
Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
|
Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
|
||||||
keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
|
keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
|
||||||
return map;
|
return map;
|
||||||
|
|||||||
+82
-41
@@ -5,13 +5,18 @@ import cn.hutool.core.map.TableMap;
|
|||||||
import cn.hutool.core.net.url.UrlBuilder;
|
import cn.hutool.core.net.url.UrlBuilder;
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
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.util.StringUtils;
|
||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +26,16 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class HttpUtils {
|
public class HttpUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码 URL 参数
|
||||||
|
*
|
||||||
|
* @param value 参数
|
||||||
|
* @return 编码后的参数
|
||||||
|
*/
|
||||||
|
public static String encodeUtf8(String value) {
|
||||||
|
return URLEncoder.encode(value, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static String replaceUrlQuery(String url, String key, String value) {
|
public static String replaceUrlQuery(String url, String key, String value) {
|
||||||
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
||||||
@@ -64,49 +79,41 @@ public class HttpUtils {
|
|||||||
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
|
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
|
||||||
|
|
||||||
if (fragment) {
|
if (fragment) {
|
||||||
buildFragmentUri(template, builder, redirectUri, query, keys);
|
StringBuilder values = new StringBuilder();
|
||||||
|
if (redirectUri.getFragment() != null) {
|
||||||
|
String append = redirectUri.getFragment();
|
||||||
|
values.append(append);
|
||||||
|
}
|
||||||
|
for (String key : query.keySet()) {
|
||||||
|
if (values.length() > 0) {
|
||||||
|
values.append("&");
|
||||||
|
}
|
||||||
|
String name = key;
|
||||||
|
if (keys != null && keys.containsKey(key)) {
|
||||||
|
name = keys.get(key);
|
||||||
|
}
|
||||||
|
values.append(name).append("={").append(key).append("}");
|
||||||
|
}
|
||||||
|
if (values.length() > 0) {
|
||||||
|
template.fragment(values.toString());
|
||||||
|
}
|
||||||
|
UriComponents encoded = template.build().expand(query).encode();
|
||||||
|
builder.fragment(encoded.getFragment());
|
||||||
} else {
|
} else {
|
||||||
buildUri(template, builder, redirectUri, query, keys);
|
for (String key : query.keySet()) {
|
||||||
|
String name = key;
|
||||||
|
if (keys != null && keys.containsKey(key)) {
|
||||||
|
name = keys.get(key);
|
||||||
|
}
|
||||||
|
template.queryParam(name, "{" + key + "}");
|
||||||
|
}
|
||||||
|
template.fragment(redirectUri.getFragment());
|
||||||
|
UriComponents encoded = template.build().expand(query).encode();
|
||||||
|
builder.query(encoded.getQuery());
|
||||||
}
|
}
|
||||||
return builder.build().toUriString();
|
return builder.build().toUriString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void buildUri(UriComponentsBuilder template, UriComponentsBuilder builder, URI redirectUri, Map<String, ?> query, Map<String, String> keys) {
|
|
||||||
for (String key : query.keySet()) {
|
|
||||||
String name = key;
|
|
||||||
if (keys != null && keys.containsKey(key)) {
|
|
||||||
name = keys.get(key);
|
|
||||||
}
|
|
||||||
template.queryParam(name, "{" + key + "}");
|
|
||||||
}
|
|
||||||
template.fragment(redirectUri.getFragment());
|
|
||||||
UriComponents encoded = template.build().expand(query).encode();
|
|
||||||
builder.query(encoded.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void buildFragmentUri(UriComponentsBuilder template, UriComponentsBuilder builder, URI redirectUri, Map<String, ?> query, Map<String, String> keys) {
|
|
||||||
StringBuilder values = new StringBuilder();
|
|
||||||
if (redirectUri.getFragment() != null) {
|
|
||||||
String append = redirectUri.getFragment();
|
|
||||||
values.append(append);
|
|
||||||
}
|
|
||||||
for (String key : query.keySet()) {
|
|
||||||
if (values.length() > 0) {
|
|
||||||
values.append("&");
|
|
||||||
}
|
|
||||||
String name = key;
|
|
||||||
if (keys != null && keys.containsKey(key)) {
|
|
||||||
name = keys.get(key);
|
|
||||||
}
|
|
||||||
values.append(name).append("={").append(key).append("}");
|
|
||||||
}
|
|
||||||
if (values.length() > 0) {
|
|
||||||
template.fragment(values.toString());
|
|
||||||
}
|
|
||||||
UriComponents encoded = template.build().expand(query).encode();
|
|
||||||
builder.fragment(encoded.getFragment());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] obtainBasicAuthorization(HttpServletRequest request) {
|
public static String[] obtainBasicAuthorization(HttpServletRequest request) {
|
||||||
String clientId;
|
String clientId;
|
||||||
String clientSecret;
|
String clientSecret;
|
||||||
@@ -117,18 +124,52 @@ public class HttpUtils {
|
|||||||
authorization = Base64.decodeStr(authorization);
|
authorization = Base64.decodeStr(authorization);
|
||||||
clientId = CharSequenceUtil.subBefore(authorization, ":", false);
|
clientId = CharSequenceUtil.subBefore(authorization, ":", false);
|
||||||
clientSecret = CharSequenceUtil.subAfter(authorization, ":", false);
|
clientSecret = CharSequenceUtil.subAfter(authorization, ":", false);
|
||||||
// 再从 Param 中获取
|
// 再从 Param 中获取
|
||||||
} else {
|
} else {
|
||||||
clientId = request.getParameter("client_id");
|
clientId = request.getParameter("client_id");
|
||||||
clientSecret = request.getParameter("client_secret");
|
clientSecret = request.getParameter("client_secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果两者非空,则返回
|
// 如果两者非空,则返回
|
||||||
if (CharSequenceUtil.isNotEmpty(clientId) && CharSequenceUtil.isNotEmpty(clientSecret)) {
|
if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {
|
||||||
return new String[]{clientId, clientSecret};
|
return new String[]{clientId, clientSecret};
|
||||||
}
|
}
|
||||||
return null;
|
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<String, String> headers, String requestBody) {
|
||||||
|
try (HttpResponse response = HttpRequest.post(url)
|
||||||
|
.addHeaders(headers)
|
||||||
|
.body(requestBody)
|
||||||
|
.execute()) {
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
|
||||||
|
*
|
||||||
|
* 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
|
||||||
|
*
|
||||||
|
* @param url URL
|
||||||
|
* @param headers 请求头
|
||||||
|
* @return 请求结果
|
||||||
|
*/
|
||||||
|
public static String get(String url, Map<String, String> headers) {
|
||||||
|
try (HttpResponse response = HttpRequest.get(url)
|
||||||
|
.addHeaders(headers)
|
||||||
|
.execute()) {
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,23 +209,6 @@
|
|||||||
<artifactId>cf-spring-boot-starter-biz-pay</artifactId>
|
<artifactId>cf-spring-boot-starter-biz-pay</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 三方云服务相关 -->
|
|
||||||
|
|
||||||
<!-- SMS SDK begin -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.aliyun</groupId>
|
|
||||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.aliyun</groupId>
|
|
||||||
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.tencentcloudapi</groupId>
|
|
||||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- SMS SDK end -->
|
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
-10
@@ -35,14 +35,4 @@ public class SmsCallbackController {
|
|||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/tencent")
|
|
||||||
@PermitAll
|
|
||||||
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
|
|
||||||
@OperateLog(enable = false)
|
|
||||||
public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
|
|
||||||
String text = ServletUtils.getBody(request);
|
|
||||||
smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
|
|
||||||
return success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.cf.imes.module.system.framework.sms.core.client;
|
package com.cf.imes.module.system.framework.sms.core.client;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||||
@@ -24,7 +24,7 @@ public interface SmsClient {
|
|||||||
* @return 短信发送结果
|
* @return 短信发送结果
|
||||||
*/
|
*/
|
||||||
SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
|
SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
|
||||||
List<Pair<String, Object>> templateParams) throws Throwable;
|
List<KeyValue<String, Object>> templateParams) throws Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析接收短信的接收结果
|
* 解析接收短信的接收结果
|
||||||
|
|||||||
-14
@@ -26,23 +26,9 @@ public abstract class AbstractSmsClient implements SmsClient {
|
|||||||
* 初始化
|
* 初始化
|
||||||
*/
|
*/
|
||||||
public final void init() {
|
public final void init() {
|
||||||
doInit();
|
|
||||||
log.debug("[init][配置({}) 初始化完成]", properties);
|
log.debug("[init][配置({}) 初始化完成]", properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义初始化
|
|
||||||
*/
|
|
||||||
protected abstract void doInit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置是否发生改变
|
|
||||||
*
|
|
||||||
* @param properties
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected abstract boolean propChanged(SmsProperties properties);
|
|
||||||
|
|
||||||
public final void refresh(SmsProperties properties) {
|
public final void refresh(SmsProperties properties) {
|
||||||
// 判断是否更新
|
// 判断是否更新
|
||||||
if (properties.equals(this.properties)) {
|
if (properties.equals(this.properties)) {
|
||||||
|
|||||||
+1
-3
@@ -5,7 +5,6 @@ import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
|
|||||||
import com.cf.imes.module.system.framework.sms.core.client.SmsClientFactory;
|
import com.cf.imes.module.system.framework.sms.core.client.SmsClientFactory;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
|
import com.cf.imes.module.system.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
|
import com.cf.imes.module.system.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.impl.tencent.TencentSmsClient;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.enums.SmsChannelEnum;
|
import com.cf.imes.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||||
import com.cf.imes.module.system.framework.sms.core.property.SmsChannelProperties;
|
import com.cf.imes.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||||
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
|
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
|
||||||
@@ -48,7 +47,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
|
|||||||
String channel = smsProperties.getChannel();
|
String channel = smsProperties.getChannel();
|
||||||
AbstractSmsClient client = channelCodeClients.get(channel);
|
AbstractSmsClient client = channelCodeClients.get(channel);
|
||||||
// 1、新的短信类型;2、配置改变
|
// 1、新的短信类型;2、配置改变
|
||||||
if (ObjectUtil.isNull(client) || client.propChanged(smsProperties)) {
|
if (ObjectUtil.isNull(client)) {
|
||||||
client = this.createSmsClient(smsProperties);
|
client = this.createSmsClient(smsProperties);
|
||||||
client.init();
|
client.init();
|
||||||
channelCodeClients.put(channel, client);
|
channelCodeClients.put(channel, client);
|
||||||
@@ -69,7 +68,6 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
|
|||||||
switch (channelEnum) {
|
switch (channelEnum) {
|
||||||
case ALIYUN: return new AliyunSmsClient(smsProperties);
|
case ALIYUN: return new AliyunSmsClient(smsProperties);
|
||||||
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(smsProperties);
|
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(smsProperties);
|
||||||
case TENCENT: return new TencentSmsClient(smsProperties);
|
|
||||||
}
|
}
|
||||||
// 创建失败,错误日志 + 抛出异常
|
// 创建失败,错误日志 + 抛出异常
|
||||||
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", smsProperties);
|
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", smsProperties);
|
||||||
|
|||||||
+128
-133
@@ -1,18 +1,17 @@
|
|||||||
package com.cf.imes.module.system.framework.sms.core.client.impl.aliyun;
|
package com.cf.imes.module.system.framework.sms.core.client.impl.aliyun;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.format.FastDateFormat;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.lang.Pair;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.aliyuncs.DefaultAcsClient;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
import com.aliyuncs.IAcsClient;
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
import com.aliyuncs.auth.Credential;
|
import cn.hutool.json.JSONArray;
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
|
import cn.hutool.json.JSONObject;
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
|
||||||
import com.aliyuncs.profile.DefaultProfile;
|
|
||||||
import com.aliyuncs.profile.IClientProfile;
|
|
||||||
import com.cf.imes.framework.common.util.collection.MapUtils;
|
import com.cf.imes.framework.common.util.collection.MapUtils;
|
||||||
|
import com.cf.imes.framework.common.util.http.HttpUtils;
|
||||||
import com.cf.imes.framework.common.util.json.JsonUtils;
|
import com.cf.imes.framework.common.util.json.JsonUtils;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||||
@@ -20,19 +19,19 @@ import com.cf.imes.module.system.framework.sms.core.client.dto.SmsTemplateRespDT
|
|||||||
import com.cf.imes.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
import com.cf.imes.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
||||||
import com.cf.imes.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
import com.cf.imes.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||||
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
|
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import lombok.Data;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList;
|
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
|
||||||
import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里短信客户端的实现类
|
* 阿里短信客户端的实现类
|
||||||
@@ -43,20 +42,11 @@ import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class AliyunSmsClient extends AbstractSmsClient {
|
public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
/**
|
private static final String URL = "https://dysmsapi.aliyuncs.com";
|
||||||
* 调用成功 code
|
private static final String HOST = "dysmsapi.aliyuncs.com";
|
||||||
*/
|
private static final String VERSION = "2017-05-25";
|
||||||
public static final String API_CODE_SUCCESS = "OK";
|
|
||||||
|
|
||||||
/**
|
private static final String RESPONSE_CODE_SUCCESS = "OK";
|
||||||
* REGION, 使用杭州
|
|
||||||
*/
|
|
||||||
private static final String ENDPOINT = "cn-hangzhou";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 阿里云客户端
|
|
||||||
*/
|
|
||||||
private volatile IAcsClient client;
|
|
||||||
|
|
||||||
public AliyunSmsClient(SmsProperties properties) {
|
public AliyunSmsClient(SmsProperties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
@@ -64,66 +54,66 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doInit() {
|
|
||||||
IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
|
|
||||||
client = new DefaultAcsClient(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean propChanged(SmsProperties properties) {
|
|
||||||
DefaultAcsClient defaultAcsClient = (DefaultAcsClient) client;
|
|
||||||
if (ObjectUtil.isNotNull(defaultAcsClient)) {
|
|
||||||
Credential credential = defaultAcsClient.getProfile().getCredential();
|
|
||||||
String accessKeyId = credential.getAccessKeyId();
|
|
||||||
if (ObjectUtil.notEqual(properties.getApiKey(), accessKeyId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
String accessSecret = credential.getAccessSecret();
|
|
||||||
if (ObjectUtil.notEqual(properties.getApiSecret(), accessSecret)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||||
List<Pair<String, Object>> templateParams) throws Throwable {
|
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||||
// 构建请求
|
Assert.notBlank(properties.getSignature(), "短信签名不能为空");
|
||||||
SendSmsRequest request = new SendSmsRequest();
|
// 1. 执行请求
|
||||||
request.setPhoneNumbers(mobile);
|
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms
|
||||||
request.setSignName(properties.getSignature());
|
TreeMap<String, Object> queryParam = new TreeMap<>();
|
||||||
request.setTemplateCode(apiTemplateId);
|
queryParam.put("PhoneNumbers", mobile);
|
||||||
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
queryParam.put("SignName", properties.getSignature());
|
||||||
request.setOutId(String.valueOf(sendLogId));
|
queryParam.put("TemplateCode", apiTemplateId);
|
||||||
// 执行请求
|
queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
||||||
SendSmsResponse response = client.getAcsResponse(request);
|
queryParam.put("OutId", sendLogId);
|
||||||
return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())
|
JSONObject response = request("SendSms", queryParam);
|
||||||
.setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage()).setMobile(mobile).setChannelCode(properties.getChannel());
|
|
||||||
|
// 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
|
@Override
|
||||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
JSONArray statuses = JSONUtil.parseArray(text);
|
||||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
|
// 字段参考 https://help.aliyun.com/zh/sms/developer-reference/smsreport-2
|
||||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
|
return convertList(statuses, status -> {
|
||||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
|
JSONObject statusObj = (JSONObject) status;
|
||||||
.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
|
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
|
@Override
|
||||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||||
// 构建请求
|
// 1. 执行请求
|
||||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate
|
||||||
request.setTemplateCode(apiTemplateId);
|
TreeMap<String, Object> queryParam = new TreeMap<>();
|
||||||
// 执行请求
|
queryParam.put("TemplateCode", apiTemplateId);
|
||||||
QuerySmsTemplateResponse response = client.getAcsResponse(request);
|
JSONObject response = request("QuerySmsTemplate", queryParam);
|
||||||
if (response.getTemplateStatus() == null) {
|
|
||||||
|
// 2.1 请求失败
|
||||||
|
String code = response.getStr("Code");
|
||||||
|
if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) {
|
||||||
|
log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
|
// 2.2 请求成功
|
||||||
.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
|
return new SmsTemplateRespDTO()
|
||||||
|
.setId(response.getStr("TemplateCode"))
|
||||||
|
.setContent(response.getStr("TemplateContent"))
|
||||||
|
.setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus")))
|
||||||
|
.setAuditReason(response.getStr("Reason"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -137,66 +127,71 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信接收状态
|
* 请求阿里云短信
|
||||||
*
|
*
|
||||||
* 参见 <a href="https://help.aliyun.com/document_detail/101867.html">文档</a>
|
* @see <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">V3 版本请求体&签名机制</>
|
||||||
*
|
* @param apiName 请求的 API 名称
|
||||||
* @author 晨丰科技
|
* @param queryParams 请求参数
|
||||||
|
* @return 请求结果
|
||||||
*/
|
*/
|
||||||
@Data
|
private JSONObject request(String apiName, TreeMap<String, Object> queryParams) {
|
||||||
public static class SmsReceiveStatus {
|
// 1. 请求参数
|
||||||
|
String queryString = queryParams.entrySet().stream()
|
||||||
|
.map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue())))
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
|
||||||
/**
|
// 2.1 请求 Header
|
||||||
* 手机号
|
TreeMap<String, String> headers = new TreeMap<>();
|
||||||
*/
|
headers.put("host", HOST);
|
||||||
@JsonProperty("phone_number")
|
headers.put("x-acs-version", VERSION);
|
||||||
private String phoneNumber;
|
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());
|
||||||
*/
|
|
||||||
@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;
|
|
||||||
|
|
||||||
|
// 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 responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody);
|
||||||
|
return JSONUtil.parseObj(responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范
|
||||||
|
*
|
||||||
|
* @param str 需要进行 URL 编码的字符串
|
||||||
|
* @return 编码后的字符串
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
private static String percentCode(String str) {
|
||||||
|
Assert.notNull(str, "str 不能为空");
|
||||||
|
return HttpUtils.encodeUtf8(str)
|
||||||
|
.replace("+", "%20") // 加号 "+" 被替换为 "%20"
|
||||||
|
.replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
|
||||||
|
.replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-14
@@ -2,13 +2,13 @@ package com.cf.imes.module.system.framework.sms.core.client.impl.debug;
|
|||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
import cn.hutool.core.codec.Base64;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.lang.Pair;
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.digest.DigestUtil;
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.framework.common.util.collection.MapUtils;
|
import com.cf.imes.framework.common.util.collection.MapUtils;
|
||||||
import com.cf.imes.framework.common.util.json.JsonUtils;
|
import com.cf.imes.framework.common.util.json.JsonUtils;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||||
@@ -38,19 +38,9 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doInit() {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean propChanged(SmsProperties properties) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||||
String apiTemplateId, List<Pair<String, Object>> templateParams) throws Throwable {
|
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||||
// 构建请求
|
// 构建请求
|
||||||
String url = buildUrl("robot/send");
|
String url = buildUrl("robot/send");
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
@@ -64,7 +54,7 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||||||
Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
|
Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
|
||||||
String errorCode = MapUtil.getStr(responseObj, "errcode");
|
String errorCode = MapUtil.getStr(responseObj, "errcode");
|
||||||
return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, "0")).setSerialNo(StrUtil.uuid())
|
return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, "0")).setSerialNo(StrUtil.uuid())
|
||||||
.setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, "errmsg")).setMobile(mobile).setChannelCode(properties.getChannel());
|
.setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, "errorMsg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,5 +89,4 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
|||||||
return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
|
return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
|
||||||
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
|
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-229
@@ -1,229 +0,0 @@
|
|||||||
package com.cf.imes.module.system.framework.sms.core.client.impl.tencent;
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
|
||||||
import cn.hutool.core.lang.Pair;
|
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
|
||||||
import com.cf.imes.framework.common.util.collection.ArrayUtils;
|
|
||||||
import com.cf.imes.framework.common.util.json.JsonUtils;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
|
||||||
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
|
|
||||||
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.DescribeSmsTemplateListRequest;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList;
|
|
||||||
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
|
||||||
import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 腾讯云短信功能实现
|
|
||||||
*
|
|
||||||
* 参见 <a href="https://cloud.tencent.com/document/product/382/52077">文档</a>
|
|
||||||
*
|
|
||||||
* @author shiwp
|
|
||||||
*/
|
|
||||||
public class TencentSmsClient extends AbstractSmsClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用成功 code
|
|
||||||
*/
|
|
||||||
public static final String API_CODE_SUCCESS = "Ok";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* REGION,使用南京
|
|
||||||
*/
|
|
||||||
private static final String ENDPOINT = "ap-nanjing";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否国际/港澳台短信:
|
|
||||||
*
|
|
||||||
* 0:表示国内短信。
|
|
||||||
* 1:表示国际/港澳台短信。
|
|
||||||
*/
|
|
||||||
private static final long INTERNATIONAL_CHINA = 0L;
|
|
||||||
|
|
||||||
private SmsClient client;
|
|
||||||
|
|
||||||
public TencentSmsClient(SmsProperties properties) {
|
|
||||||
super(properties);
|
|
||||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
|
||||||
validateSdkAppId(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doInit() {
|
|
||||||
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
|
|
||||||
Credential credential = new Credential(getApiKey(), properties.getApiSecret());
|
|
||||||
client = new SmsClient(credential, ENDPOINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean propChanged(SmsProperties properties) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参数校验腾讯云的 SDK AppId
|
|
||||||
*
|
|
||||||
* 原因是:腾讯云发放短信的时候,需要额外的参数 sdkAppId
|
|
||||||
*
|
|
||||||
* 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
|
||||||
*
|
|
||||||
* @param properties 配置
|
|
||||||
*/
|
|
||||||
private static void validateSdkAppId(SmsProperties properties) {
|
|
||||||
String combineKey = properties.getApiKey();
|
|
||||||
Assert.notEmpty(combineKey, "apiKey 不能为空");
|
|
||||||
String[] keys = combineKey.trim().split(" ");
|
|
||||||
Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSdkAppId() {
|
|
||||||
return CharSequenceUtil.subAfter(properties.getApiKey(), " ", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getApiKey() {
|
|
||||||
return CharSequenceUtil.subBefore(properties.getApiKey(), " ", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
|
||||||
String apiTemplateId, List<Pair<String, Object>> 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()).setChannelCode(properties.getChannel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
|
||||||
List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
|
||||||
return convertList(callback, status -> new SmsReceiveRespDTO()
|
|
||||||
.setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
|
|
||||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())
|
|
||||||
.setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())
|
|
||||||
.setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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());
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Integer convertSmsTemplateAuditStatus(int templateStatus) {
|
|
||||||
switch (templateStatus) {
|
|
||||||
case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
|
|
||||||
case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
|
|
||||||
case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
|
|
||||||
default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
private static class SmsReceiveStatus {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信接受成功 code
|
|
||||||
*/
|
|
||||||
public static final String SUCCESS_CODE = "SUCCESS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户实际接收到短信的时间
|
|
||||||
*/
|
|
||||||
@JsonProperty("user_receive_time")
|
|
||||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
|
||||||
private LocalDateTime receiveTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 国家(或地区)码
|
|
||||||
*/
|
|
||||||
@JsonProperty("nationcode")
|
|
||||||
private String nationCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号码
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实际是否收到短信接收状态,SUCCESS(成功)、FAIL(失败)
|
|
||||||
*/
|
|
||||||
@JsonProperty("report_status")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户接收短信状态码错误信息
|
|
||||||
*/
|
|
||||||
@JsonProperty("errmsg")
|
|
||||||
private String errCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户接收短信状态描述
|
|
||||||
*/
|
|
||||||
@JsonProperty("description")
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 本次发送标识 ID(与发送接口返回的SerialNo对应)
|
|
||||||
*/
|
|
||||||
@JsonProperty("sid")
|
|
||||||
private String serialNo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户的 session 内容(与发送接口的请求参数 SessionContext 一致)
|
|
||||||
*/
|
|
||||||
@JsonProperty("ext")
|
|
||||||
private SessionContext sessionContext;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
@Data
|
|
||||||
static class SessionContext {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送短信记录id
|
|
||||||
*/
|
|
||||||
private Long logId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+1
-4
@@ -15,10 +15,7 @@ import lombok.Getter;
|
|||||||
public enum SmsChannelEnum {
|
public enum SmsChannelEnum {
|
||||||
|
|
||||||
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
|
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
|
||||||
ALIYUN("ALIYUN", "阿里云"),
|
ALIYUN("ALIYUN", "阿里云");
|
||||||
TENCENT("TENCENT", "腾讯云"),
|
|
||||||
// HUA_WEI("HUA_WEI", "华为云"),
|
|
||||||
;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编码
|
* 编码
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.cf.imes.module.system.mq.message.sms;
|
package com.cf.imes.module.system.mq.message.sms;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
@@ -37,7 +37,7 @@ public class SmsSendMessage {
|
|||||||
/**
|
/**
|
||||||
* 短信模板参数
|
* 短信模板参数
|
||||||
*/
|
*/
|
||||||
private List<Pair<String, Object>> templateParams;
|
private List<KeyValue<String, Object>> templateParams;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信消息模板类型:system_sms_template.type
|
* 短信消息模板类型:system_sms_template.type
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.cf.imes.module.system.mq.producer.sms;
|
package com.cf.imes.module.system.mq.producer.sms;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
|
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||||
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
|
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -32,7 +32,7 @@ public class SmsProducer {
|
|||||||
* @param template 模板信息
|
* @param template 模板信息
|
||||||
* @param templateParams 短信模板参数
|
* @param templateParams 短信模板参数
|
||||||
*/
|
*/
|
||||||
public void sendSmsSendMessage(Long logId, String mobile, SmsTemplateDO template, List<Pair<String, Object>> templateParams) {
|
public void sendSmsSendMessage(Long logId, String mobile, SmsTemplateDO template, List<KeyValue<String, Object>> templateParams) {
|
||||||
SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
|
SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
|
||||||
message.setChannelId(template.getChannelId()).setApiTemplateId(template.getApiTemplateId()).setTemplateType(template.getType()).setTemplateParams(templateParams);
|
message.setChannelId(template.getChannelId()).setApiTemplateId(template.getApiTemplateId()).setTemplateType(template.getType()).setTemplateParams(templateParams);
|
||||||
// event异步发送短信,保证子线程内部request不为空
|
// event异步发送短信,保证子线程内部request不为空
|
||||||
|
|||||||
+5
-5
@@ -3,7 +3,7 @@ package com.cf.imes.module.system.service.sms;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
@@ -86,7 +86,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
|||||||
// 校验手机号码是否存在
|
// 校验手机号码是否存在
|
||||||
mobile = validateMobile(mobile);
|
mobile = validateMobile(mobile);
|
||||||
// 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
|
// 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
|
||||||
List<Pair<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);
|
List<KeyValue<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);
|
||||||
|
|
||||||
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
|
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
|
||||||
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
|
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
|
||||||
@@ -121,13 +121,13 @@ public class SmsSendServiceImpl implements SmsSendService {
|
|||||||
* @return 处理后的参数
|
* @return 处理后的参数
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
List<Pair<String, Object>> buildTemplateParams(SmsTemplateDO template, Map<String, Object> templateParams) {
|
List<KeyValue<String, Object>> buildTemplateParams(SmsTemplateDO template, Map<String, Object> templateParams) {
|
||||||
return template.getParams().stream().map(key -> {
|
return template.getParams().stream().map(key -> {
|
||||||
Object value = templateParams.get(key);
|
Object value = templateParams.get(key);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key);
|
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key);
|
||||||
}
|
}
|
||||||
return new Pair<>(key, value);
|
return new KeyValue<>(key, value);
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
|||||||
SmsClient smsClient = smsChannelService.getSmsClient();
|
SmsClient smsClient = smsChannelService.getSmsClient();
|
||||||
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
|
Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
|
||||||
|
|
||||||
List<Pair<String, Object>> templateParams = message.getTemplateParams();
|
List<KeyValue<String, Object>> templateParams = message.getTemplateParams();
|
||||||
String channelCode = null;
|
String channelCode = null;
|
||||||
// 发送短信
|
// 发送短信
|
||||||
try {
|
try {
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.cf.imes.module.system.service.sms.handler;
|
package com.cf.imes.module.system.service.sms.handler;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -21,5 +21,5 @@ public interface SmsSendAfterSendHandler {
|
|||||||
boolean checkTemplateType(Integer templateType);
|
boolean checkTemplateType(Integer templateType);
|
||||||
|
|
||||||
|
|
||||||
void afterSend(SmsSendRespDTO sendResponse, List<Pair<String, Object>> params);
|
void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.cf.imes.module.system.service.sms.handler.impl;
|
package com.cf.imes.module.system.service.sms.handler.impl;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||||
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
|
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
|
||||||
import com.cf.imes.module.system.enums.sms.SmsTemplateTypeEnum;
|
import com.cf.imes.module.system.enums.sms.SmsTemplateTypeEnum;
|
||||||
@@ -33,10 +33,10 @@ public class SmsSendAfterSendHandlerImpl implements SmsSendAfterSendHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterSend(SmsSendRespDTO sendResponse, List<Pair<String, Object>> params) {
|
public void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params) {
|
||||||
// 发送成功把验证码存入redis
|
// 发送成功把验证码存入redis
|
||||||
if (sendResponse.getSuccess()) {
|
if (sendResponse.getSuccess()) {
|
||||||
for (Pair<String, Object> keyValue : params) {
|
for (KeyValue<String, Object> keyValue : params) {
|
||||||
if ("code".equals(keyValue.getKey())) {
|
if ("code".equals(keyValue.getKey())) {
|
||||||
redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
|
redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
package com.cf.imes.module.system.service.sms;
|
package com.cf.imes.module.system.service.sms;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Pair;
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.cf.imes.framework.common.core.KeyValue;
|
||||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||||
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
import com.cf.imes.framework.common.enums.UserTypeEnum;
|
||||||
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
|
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
|
||||||
@@ -87,7 +87,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
|||||||
assertEquals(smsLogId, resultSmsLogId);
|
assertEquals(smsLogId, resultSmsLogId);
|
||||||
// 断言调用
|
// 断言调用
|
||||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()), eq(template),
|
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()), eq(template),
|
||||||
eq(Lists.newArrayList(new Pair<>("code", "1234"), new Pair<>("op", "login"))));
|
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -125,7 +125,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
|||||||
assertEquals(smsLogId, resultSmsLogId);
|
assertEquals(smsLogId, resultSmsLogId);
|
||||||
// 断言调用
|
// 断言调用
|
||||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
||||||
eq(Lists.newArrayList(new Pair<>("code", "1234"), new Pair<>("op", "login"))));
|
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +164,7 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest {
|
|||||||
assertEquals(smsLogId, resultSmsLogId);
|
assertEquals(smsLogId, resultSmsLogId);
|
||||||
// 断言调用
|
// 断言调用
|
||||||
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(template),
|
||||||
eq(Lists.newArrayList(new Pair<>("code", "1234"), new Pair<>("op", "login"))));
|
eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user