map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
+ keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
+ return map;
+ }
+
}
diff --git a/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java b/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
index 3ae810982a..78ec483928 100644
--- a/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
+++ b/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
@@ -9,6 +9,11 @@ import java.util.Date;
*/
public class DateUtils {
+ /**
+ * 时区 - 默认
+ */
+ public static final String TIME_ZONE_DEFAULT = "GMT+8";
+
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
public static Date addTime(Duration duration) {
diff --git a/src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java b/src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
index f6727459c5..2291235dc0 100644
--- a/src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
+++ b/src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
@@ -7,7 +7,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
/**
* JSON 工具类
@@ -20,7 +21,7 @@ public class JsonUtils {
/**
* 初始化 objectMapper 属性
- *
+ *
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
*
* @param objectMapper ObjectMapper 对象
@@ -59,7 +60,7 @@ public class JsonUtils {
}
}
- public static Object parseObject(String text, TypeReference> typeReference) {
+ public static T parseObject(String text, TypeReference typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
@@ -67,4 +68,15 @@ public class JsonUtils {
}
}
+ public static List parseArray(String text, Class clazz) {
+ if (StrUtil.isEmpty(text)) {
+ return new ArrayList<>();
+ }
+ try {
+ return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
diff --git a/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java b/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java
index 5e98d915b6..e0ca605a41 100644
--- a/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java
+++ b/src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java
@@ -1,7 +1,10 @@
package cn.iocoder.dashboard.util.string;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import java.util.Map;
+
/**
* 字符串工具类
*
@@ -13,4 +16,22 @@ public class StrUtils {
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
}
+ /**
+ * 指定字符串的
+ * @param str
+ * @param replaceMap
+ * @return
+ */
+ public static String replace(String str, Map replaceMap) {
+ assert StrUtil.isNotBlank(str);
+ if (ObjectUtil.isEmpty(replaceMap)) {
+ return str;
+ }
+ String result = null;
+ for (String key : replaceMap.keySet()) {
+ result = str.replace(key, replaceMap.get(key));
+ }
+ return result;
+ }
+
}
diff --git a/src/main/resources/codegen/java/service/service.vm b/src/main/resources/codegen/java/service/service.vm
index 04499314ed..17a9b39532 100644
--- a/src/main/resources/codegen/java/service/service.vm
+++ b/src/main/resources/codegen/java/service/service.vm
@@ -63,7 +63,7 @@ public interface ${table.className}Service {
* 获得${table.classComment}列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
- * @return ${table.classComment}分页
+ * @return ${table.classComment}列表
*/
List<${table.className}DO> get${simpleClassName}List(${table.className}ExportReqVO exportReqVO);
diff --git a/src/main/resources/codegen/vue/views/index.vue.vm b/src/main/resources/codegen/vue/views/index.vue.vm
index 73115fd777..5456d88ae4 100644
--- a/src/main/resources/codegen/vue/views/index.vue.vm
+++ b/src/main/resources/codegen/vue/views/index.vue.vm
@@ -76,7 +76,7 @@
{{ getDictDataLabel(DICT_TYPE.$dictType.toUpperCase(), scope.row.${column.javaField}) }}
- >
+
#else
#end
@@ -137,7 +137,7 @@
#elseif($column.htmlType == "checkbox")## 多选框
-
+
#if ("" != $dictType)## 有数据字典
#elseif($column.htmlType == "radio")## 单选框
-
+
#if ("" != $dictType)## 有数据字典
> templateParams = new ArrayList<>();
+ templateParams.add(new KeyValue<>("code", "1024"));
+// templateParams.put("operation", "嘿嘿");
+// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
+ SmsCommonResult result = smsClient.sendSms(1L, "15601691399",
+ "SMS_207945135", templateParams);
+ System.out.println(result);
+ }
+
+ @Test
+ public void testGetSmsTemplate() {
+ String apiTemplateId = "SMS_2079451351";
+ SmsCommonResult result = smsClient.getSmsTemplate(apiTemplateId);
+ System.out.println(result);
+ }
+
+}
diff --git a/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java
new file mode 100644
index 0000000000..5815aa75c8
--- /dev/null
+++ b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java
@@ -0,0 +1,45 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
+
+import cn.iocoder.dashboard.common.core.KeyValue;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link DebugDingTalkSmsClient} 的集成测试
+ */
+public class DebugDingTalkSmsClientIntegrationTest {
+
+ private static DebugDingTalkSmsClient smsClient;
+
+ @BeforeAll
+ public static void init() {
+ // 创建配置类
+ SmsChannelProperties properties = new SmsChannelProperties();
+ properties.setId(1L);
+ properties.setSignature("芋道");
+ properties.setCode(SmsChannelEnum.DEBUG_DING_TALK.getCode());
+ properties.setApiKey("696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859");
+ properties.setApiSecret("SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67");
+ // 创建客户端
+ smsClient = new DebugDingTalkSmsClient(properties);
+ smsClient.init();
+ }
+
+ @Test
+ public void testSendSms() {
+ List> templateParams = new ArrayList<>();
+ templateParams.add(new KeyValue<>("code", "1024"));
+ templateParams.add(new KeyValue<>("operation", "嘿嘿"));
+// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
+ SmsCommonResult result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
+ System.out.println(result);
+ }
+
+}
diff --git a/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java
new file mode 100644
index 0000000000..73f1a472df
--- /dev/null
+++ b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java
@@ -0,0 +1,52 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
+
+import cn.iocoder.dashboard.common.core.KeyValue;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link YunpianSmsClient} 的集成测试
+ */
+public class YunpianSmsClientIntegrationTest {
+
+ private static YunpianSmsClient smsClient;
+
+ @BeforeAll
+ public static void init() {
+ // 创建配置类
+ SmsChannelProperties properties = new SmsChannelProperties();
+ properties.setId(1L);
+ properties.setSignature("芋道");
+ properties.setCode(SmsChannelEnum.YUN_PIAN.getCode());
+ properties.setApiKey("1555a14277cb8a608cf45a9e6a80d510");
+ // 创建客户端
+ smsClient = new YunpianSmsClient(properties);
+ smsClient.init();
+ }
+
+ @Test
+ public void testSendSms() {
+ List> templateParams = new ArrayList<>();
+ templateParams.add(new KeyValue<>("code", "1024"));
+ templateParams.add(new KeyValue<>("operation", "嘿嘿"));
+// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
+ SmsCommonResult result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
+ System.out.println(result);
+ }
+
+ @Test
+ public void testGetSmsTemplate() {
+ String apiTemplateId = "4383920";
+ SmsCommonResult result = smsClient.getSmsTemplate(apiTemplateId);
+ System.out.println(result);
+ }
+
+}
diff --git a/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/package-info.java b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/package-info.java
new file mode 100644
index 0000000000..037ce8ca27
--- /dev/null
+++ b/src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.dashboard.framework.sms.core.client;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java b/src/test-integration/java/cn/iocoder/dashboard/modules/system/service/package-info.java
similarity index 100%
rename from src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java
rename to src/test-integration/java/cn/iocoder/dashboard/modules/system/service/package-info.java
diff --git a/src/test-integration/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceIntegrationTest.java b/src/test-integration/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceIntegrationTest.java
new file mode 100644
index 0000000000..7ec704b5db
--- /dev/null
+++ b/src/test-integration/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceIntegrationTest.java
@@ -0,0 +1,74 @@
+package cn.iocoder.dashboard.modules.system.service.sms;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.iocoder.dashboard.BaseDbAndRedisIntegrationTest;
+import cn.iocoder.dashboard.common.enums.UserTypeEnum;
+import cn.iocoder.dashboard.framework.sms.config.SmsConfiguration;
+import cn.iocoder.dashboard.modules.system.mq.consumer.sms.SysSmsSendConsumer;
+import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
+import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsChannelServiceImpl;
+import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsLogServiceImpl;
+import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsServiceImpl;
+import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsTemplateServiceImpl;
+import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Import({SmsConfiguration.class,
+ SysSmsChannelServiceImpl.class, SysSmsServiceImpl.class, SysSmsTemplateServiceImpl.class, SysSmsLogServiceImpl.class,
+ SysSmsProducer.class, SysSmsSendConsumer.class})
+public class SysSmsServiceIntegrationTest extends BaseDbAndRedisIntegrationTest {
+
+ @Resource
+ private SysSmsServiceImpl smsService;
+ @Resource
+ private SysSmsChannelServiceImpl smsChannelService;
+
+ @MockBean
+ private SysUserService userService;
+
+ @Test
+ public void testSendSingleSms_yunpianSuccess() {
+ // 参数准备
+ String mobile = "15601691399";
+ Long userId = 1L;
+ Integer userType = UserTypeEnum.ADMIN.getValue();
+ String templateCode = "test_01";
+ Map templateParams = MapUtil.builder()
+ .put("operation", "登陆").put("code", "1234").build();
+ // 调用
+ smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
+
+ // 等待 MQ 消费
+ ThreadUtil.sleep(1, TimeUnit.HOURS);
+ }
+
+ @Test
+ public void testSendSingleSms_aliyunSuccess() {
+ // 参数准备
+ String mobile = "15601691399";
+ Long userId = 1L;
+ Integer userType = UserTypeEnum.ADMIN.getValue();
+ String templateCode = "test_02";
+ Map templateParams = MapUtil.builder()
+ .put("code", "1234").build();
+ // 调用
+ smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
+
+ // 等待 MQ 消费
+ ThreadUtil.sleep(1, TimeUnit.HOURS);
+ }
+
+// @Test
+// public void testDoSendSms() {
+// // 等待 MQ 消费
+// ThreadUtil.sleep(1, TimeUnit.HOURS);
+// }
+
+}
diff --git a/src/test-integration/resources/application-integration-test.yaml b/src/test-integration/resources/application-integration-test.yaml
index 88b92273c6..43a846ee2f 100644
--- a/src/test-integration/resources/application-integration-test.yaml
+++ b/src/test-integration/resources/application-integration-test.yaml
@@ -9,19 +9,15 @@ spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
- url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
- driver-class-name: org.h2.Driver
- username: sa
- password:
- schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
- druid:
- async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
- initial-size: 1 # 单元测试,配置为 1,提升启动速度
+ url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
- port: 6379 # 端口(单元测试,使用 16379 端口)
+ port: 6379 # 端口
database: 0 # 数据库索引
mybatis:
diff --git a/src/test/java/cn/iocoder/dashboard/BaseMockitoUnitTest.java b/src/test/java/cn/iocoder/dashboard/BaseMockitoUnitTest.java
new file mode 100644
index 0000000000..4a595b24e8
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/BaseMockitoUnitTest.java
@@ -0,0 +1,13 @@
+package cn.iocoder.dashboard;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * 纯 Mockito 的单元测试
+ *
+ * @author 芋道源码
+ */
+@ExtendWith(MockitoExtension.class)
+public class BaseMockitoUnitTest {
+}
diff --git a/src/test/java/cn/iocoder/dashboard/framework/package-info.java b/src/test/java/cn/iocoder/dashboard/framework/package-info.java
new file mode 100644
index 0000000000..0274647fbd
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/framework/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.dashboard.framework;
diff --git a/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
new file mode 100644
index 0000000000..a544d03d5a
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
@@ -0,0 +1,224 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.dashboard.BaseMockitoUnitTest;
+import cn.iocoder.dashboard.common.core.KeyValue;
+import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import cn.iocoder.dashboard.util.collection.MapUtils;
+import cn.iocoder.dashboard.util.date.DateUtils;
+import com.aliyuncs.AcsRequest;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
+import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
+import com.aliyuncs.exceptions.ClientException;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.List;
+import java.util.function.Function;
+
+import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR;
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static cn.iocoder.dashboard.util.json.JsonUtils.toJsonString;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link AliyunSmsClient} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class AliyunSmsClientTest extends BaseMockitoUnitTest {
+
+ private final SmsChannelProperties properties = new SmsChannelProperties()
+ .setApiKey(randomString()) // 随机一个 apiKey,避免构建报错
+ .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
+ .setSignature("芋道源码");
+
+ @InjectMocks
+ private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
+
+ @Mock
+ private IAcsClient client;
+
+ @Test
+ public void testDoInit() {
+ // 准备参数
+ // mock 方法
+
+ // 调用
+ smsClient.doInit();
+ // 断言
+ assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDoSendSms() throws ClientException {
+ // 准备参数
+ Long sendLogId = randomLongId();
+ String mobile = randomString();
+ String apiTemplateId = randomString();
+ List> templateParams = Lists.newArrayList(
+ new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
+ // mock 方法
+ SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK"));
+ when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> {
+ assertEquals(mobile, acsRequest.getPhoneNumbers());
+ assertEquals(properties.getSignature(), acsRequest.getSignName());
+ assertEquals(apiTemplateId, acsRequest.getTemplateCode());
+ assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
+ assertEquals(sendLogId.toString(), acsRequest.getOutId());
+ return true;
+ }))).thenReturn(response);
+
+ // 调用
+ SmsCommonResult result = smsClient.doSendSms(sendLogId, mobile,
+ apiTemplateId, templateParams);
+ // 断言
+ assertEquals(response.getCode(), result.getApiCode());
+ assertEquals(response.getMessage(), result.getApiMsg());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ assertEquals(response.getRequestId(), result.getApiRequestId());
+ // 断言结果
+ assertEquals(response.getBizId(), result.getData().getSerialNo());
+ }
+
+ @Test
+ public void testDoTParseSmsReceiveStatus() throws Throwable {
+ // 准备参数
+ String text = "[\n" +
+ " {\n" +
+ " \"phone_number\" : \"13900000001\",\n" +
+ " \"send_time\" : \"2017-01-01 11:12:13\",\n" +
+ " \"report_time\" : \"2017-02-02 22:23:24\",\n" +
+ " \"success\" : true,\n" +
+ " \"err_code\" : \"DELIVERED\",\n" +
+ " \"err_msg\" : \"用户接收成功\",\n" +
+ " \"sms_size\" : \"1\",\n" +
+ " \"biz_id\" : \"12345\",\n" +
+ " \"out_id\" : \"67890\"\n" +
+ " }\n" +
+ "]";
+ // mock 方法
+
+ // 调用
+ List statuses = smsClient.doParseSmsReceiveStatus(text);
+ // 断言
+ assertEquals(1, statuses.size());
+ assertTrue(statuses.get(0).getSuccess());
+ assertEquals("DELIVERED", statuses.get(0).getErrorCode());
+ assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
+ assertEquals("13900000001", statuses.get(0).getMobile());
+ assertEquals(DateUtils.buildTime(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
+ assertEquals("12345", statuses.get(0).getSerialNo());
+ assertEquals(67890L, statuses.get(0).getLogId());
+ }
+
+ @Test
+ public void testDoGetSmsTemplate() throws ClientException {
+ // 准备参数
+ String apiTemplateId = randomString();
+ // mock 方法
+ QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
+ o.setCode("OK");
+ o.setTemplateStatus(1); // 设置模板通过
+ });
+ when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> {
+ assertEquals(apiTemplateId, acsRequest.getTemplateCode());
+ return true;
+ }))).thenReturn(response);
+
+ // 调用
+ SmsCommonResult result = smsClient.doGetSmsTemplate(apiTemplateId);
+ // 断言
+ assertEquals(response.getCode(), result.getApiCode());
+ assertEquals(response.getMessage(), result.getApiMsg());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ assertEquals(response.getRequestId(), result.getApiRequestId());
+ // 断言结果
+ assertEquals(response.getTemplateCode(), result.getData().getId());
+ assertEquals(response.getTemplateContent(), result.getData().getContent());
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
+ assertEquals(response.getReason(), result.getData().getAuditReason());
+ }
+
+ @Test
+ public void testConvertSmsTemplateAuditStatus() {
+ assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus(0));
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus(1));
+ assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus(2));
+ assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),
+ "未知审核状态(3)");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInvoke_throwable() throws ClientException {
+ // 准备参数
+ QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
+ // mock 方法
+ ClientException ex = new ClientException("isv.INVALID_PARAMETERS", "参数不正确", randomString());
+ when(client.getAcsResponse(any(AcsRequest.class))).thenThrow(ex);
+
+ // 调用,并断言异常
+ SmsCommonResult> result = smsClient.invoke(request,null);
+ // 断言
+ assertEquals(ex.getErrCode(), result.getApiCode());
+ assertEquals(ex.getErrMsg(), result.getApiMsg());
+ assertEquals(SMS_API_PARAM_ERROR.getCode(), result.getCode());
+ assertEquals(SMS_API_PARAM_ERROR.getMsg(), result.getMsg());
+ assertEquals(ex.getRequestId(), result.getApiRequestId());
+ }
+
+ @Test
+ public void testInvoke_success() throws ClientException {
+ // 准备参数
+ QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
+ Function responseConsumer = response -> {
+ SmsTemplateRespDTO data = new SmsTemplateRespDTO();
+ data.setId(response.getTemplateCode()).setContent(response.getTemplateContent());
+ data.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(response.getReason());
+ return data;
+ };
+ // mock 方法
+ QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
+ o.setCode("OK");
+ o.setTemplateStatus(1); // 设置模板通过
+ });
+ when(client.getAcsResponse(any(AcsRequest.class))).thenReturn(response);
+
+ // 调用
+ SmsCommonResult result = smsClient.invoke(request, responseConsumer);
+ // 断言
+ assertEquals(response.getCode(), result.getApiCode());
+ assertEquals(response.getMessage(), result.getApiMsg());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ assertEquals(response.getRequestId(), result.getApiRequestId());
+ // 断言结果
+ assertEquals(response.getTemplateCode(), result.getData().getId());
+ assertEquals(response.getTemplateContent(), result.getData().getContent());
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
+ assertEquals(response.getReason(), result.getData().getAuditReason());
+ }
+
+}
diff --git a/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsCodeMappingTest.java b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsCodeMappingTest.java
new file mode 100644
index 0000000000..54dba079b2
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsCodeMappingTest.java
@@ -0,0 +1,43 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
+
+import cn.iocoder.dashboard.BaseMockitoUnitTest;
+import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link AliyunSmsCodeMapping} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class AliyunSmsCodeMappingTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ private AliyunSmsCodeMapping codeMapping;
+
+ @Test
+ public void testApply() {
+ assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("OK"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("MissingAccessKeyId"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_NOT_EXISTS"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_ABNORMAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("isv.DAY_LIMIT_CONTROL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("isv.SMS_CONTENT_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGN_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SIGN_NAME_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("isp.RAM_PERMISSION_DENY"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.OUT_OF_SERVICE"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.AMOUNT_NOT_ENOUGH"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("isv.SMS_TEMPLATE_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGNATURE_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_PARAMETERS"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_JSON_PARAM"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("isv.MOBILE_NUMBER_ILLEGAL"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("isv.TEMPLATE_MISSING_PARAMETERS"));
+ assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("isv.BUSINESS_LIMIT_CONTROL"));
+ }
+
+}
diff --git a/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java
new file mode 100644
index 0000000000..3e4190c0a5
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java
@@ -0,0 +1,202 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.dashboard.BaseMockitoUnitTest;
+import cn.iocoder.dashboard.common.core.KeyValue;
+import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
+import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
+import cn.iocoder.dashboard.util.date.DateUtils;
+import com.google.common.collect.Lists;
+import com.yunpian.sdk.YunpianClient;
+import com.yunpian.sdk.api.SmsApi;
+import com.yunpian.sdk.api.TplApi;
+import com.yunpian.sdk.constant.YunpianConstant;
+import com.yunpian.sdk.model.Result;
+import com.yunpian.sdk.model.SmsSingleSend;
+import com.yunpian.sdk.model.Template;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static com.yunpian.sdk.constant.Code.OK;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * 对 {@link YunpianSmsClient} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class YunpianSmsClientTest extends BaseMockitoUnitTest {
+
+ private final SmsChannelProperties properties = new SmsChannelProperties()
+ .setApiKey(randomString()); // 随机一个 apiKey,避免构建报错
+
+ @InjectMocks
+ private final YunpianSmsClient smsClient = new YunpianSmsClient(properties);
+
+ @Mock
+ private YunpianClient client;
+
+ @Test
+ public void testDoInit() {
+ // 准备参数
+ // mock 方法
+
+ // 调用
+ smsClient.doInit();
+ // 断言
+ assertNotEquals(client, ReflectUtil.getFieldValue(smsClient, "client"));
+ verify(client, times(1)).close();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDoSendSms() throws Throwable {
+ // 准备参数
+ Long sendLogId = randomLongId();
+ String mobile = randomString();
+ String apiTemplateId = randomString();
+ List> templateParams = Lists.newArrayList(
+ new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
+ // mock sms 方法
+ SmsApi smsApi = mock(SmsApi.class);
+ when(client.sms()).thenReturn(smsApi);
+ // mock tpl_single_send 方法
+ Map request = new HashMap<>();
+ request.put(YunpianConstant.MOBILE, mobile);
+ request.put(YunpianConstant.TPL_ID, apiTemplateId);
+ request.put(YunpianConstant.TPL_VALUE, "#code#=1234op#=login");
+ request.put(YunpianConstant.UID, String.valueOf(sendLogId));
+ request.put(YunpianConstant.CALLBACK_URL, properties.getCallbackUrl());
+ Result responseResult = randomPojo(Result.class, SmsSingleSend.class,
+ o -> o.setCode(OK)); // API 发送成功的 code
+ when(smsApi.tpl_single_send(eq(request))).thenReturn(responseResult);
+
+ // 调用
+ SmsCommonResult result = smsClient.doSendSms(sendLogId, mobile,
+ apiTemplateId, templateParams);
+ // 断言
+ assertEquals(String.valueOf(responseResult.getCode()), result.getApiCode());
+ assertEquals(responseResult.getMsg() + " => " + responseResult.getDetail(), result.getApiMsg());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ assertNull(result.getApiRequestId());
+ // 断言结果
+ assertEquals(String.valueOf(responseResult.getData().getSid()), result.getData().getSerialNo());
+ }
+
+ @Test
+ public void testDoParseSmsReceiveStatus() throws Throwable {
+ // 准备参数
+ String text = "[{\"sid\":9527,\"uid\":1024,\"user_receive_time\":\"2014-03-17 22:55:21\",\"error_msg\":\"\",\"mobile\":\"15205201314\",\"report_status\":\"SUCCESS\"}]";
+ // mock 方法
+
+ // 调用
+
+ // 断言
+ // 调用
+ List statuses = smsClient.doParseSmsReceiveStatus(text);
+ // 断言
+ assertEquals(1, statuses.size());
+ assertTrue(statuses.get(0).getSuccess());
+ assertEquals("", statuses.get(0).getErrorCode());
+ assertNull(statuses.get(0).getErrorMsg());
+ assertEquals("15205201314", statuses.get(0).getMobile());
+ assertEquals(DateUtils.buildTime(2014, 3, 17, 22, 55, 21), statuses.get(0).getReceiveTime());
+ assertEquals("9527", statuses.get(0).getSerialNo());
+ assertEquals(1024L, statuses.get(0).getLogId());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDoGetSmsTemplate() throws Throwable {
+ // 准备参数
+ String apiTemplateId = randomString();
+ // mock tpl 方法
+ TplApi tplApi = mock(TplApi.class);
+ when(client.tpl()).thenReturn(tplApi);
+ // mock get 方法
+ Map request = new HashMap<>();
+ request.put(YunpianConstant.APIKEY, properties.getApiKey());
+ request.put(YunpianConstant.TPL_ID, apiTemplateId);
+ Result> responseResult = randomPojo(Result.class, List.class, o -> {
+ o.setCode(OK); // API 发送成功的 code
+ o.setData(randomPojoList(Template.class, t -> t.setCheck_status("SUCCESS")));
+ });
+ when(tplApi.get(eq(request))).thenReturn(responseResult);
+
+ // 调用
+ SmsCommonResult result = smsClient.doGetSmsTemplate(apiTemplateId);
+ // 断言
+ assertEquals(String.valueOf(responseResult.getCode()), result.getApiCode());
+ assertEquals(responseResult.getMsg() + " => " + responseResult.getDetail(), result.getApiMsg());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+ assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ assertNull(result.getApiRequestId());
+ // 断言结果
+ Template template = responseResult.getData().get(0);
+ assertEquals(template.getTpl_id().toString(), result.getData().getId());
+ assertEquals(template.getTpl_content(), result.getData().getContent());
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
+ assertEquals(template.getReason(), result.getData().getAuditReason());
+ }
+
+ @Test
+ public void testConvertSmsTemplateAuditStatus() {
+ assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus("CHECKING"));
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus("SUCCESS"));
+ assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
+ smsClient.convertSmsTemplateAuditStatus("FAIL"));
+ assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("test"),
+ "未知审核状态(test)");
+ }
+
+ @Test
+ public void testInvoke_throwable() {
+ // 准备参数
+ Supplier> requestConsumer =
+ () -> new Result<>().setThrowable(new NullPointerException());
+ // mock 方法
+
+ // 调用,并断言异常
+ assertThrows(NullPointerException.class,
+ () -> smsClient.invoke(requestConsumer, null));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInvoke_success() throws Throwable {
+ // 准备参数
+ Result responseResult = randomPojo(Result.class, SmsSingleSend.class, o -> o.setCode(OK));
+ Supplier