mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-25 00:15:06 +08:00
✨ 全局:将 sms 融合到 system 模块里,减少 starter
This commit is contained in:
@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
@ -1,9 +0,0 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(SmsCodeProperties.class)
|
||||
public class SmsCodeConfiguration {
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms;
|
||||
package cn.iocoder.yudao.module.system.framework.sms.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.config;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.impl.SmsClientFactoryImpl;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 短信配置类,包括短信客户端、短信验证码两部分
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(SmsCodeProperties.class)
|
||||
public class SmsConfiguration {
|
||||
|
||||
@Bean
|
||||
public SmsClientFactory smsClientFactory() {
|
||||
return new SmsClientFactoryImpl();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 短信客户端,用于对接各短信平台的 SDK,实现短信发送等功能
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/1/25 14:14
|
||||
*/
|
||||
public interface SmsClient {
|
||||
|
||||
/**
|
||||
* 获得渠道编号
|
||||
*
|
||||
* @return 渠道编号
|
||||
*/
|
||||
Long getId();
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param logId 日志编号
|
||||
* @param mobile 手机号
|
||||
* @param apiTemplateId 短信 API 的模板编号
|
||||
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
|
||||
* @return 短信发送结果
|
||||
*/
|
||||
SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable;
|
||||
|
||||
/**
|
||||
* 解析接收短信的接收结果
|
||||
*
|
||||
* @param text 结果
|
||||
* @return 结果内容
|
||||
* @throws Throwable 当解析 text 发生异常时,则会抛出异常
|
||||
*/
|
||||
List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;
|
||||
|
||||
/**
|
||||
* 查询指定的短信模板
|
||||
*
|
||||
* @param apiTemplateId 短信 API 的模板编号
|
||||
* @return 短信模板
|
||||
*/
|
||||
SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable;
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
|
||||
/**
|
||||
* 短信客户端的工厂接口
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/1/28 14:01
|
||||
*/
|
||||
public interface SmsClientFactory {
|
||||
|
||||
/**
|
||||
* 获得短信 Client
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @return 短信 Client
|
||||
*/
|
||||
SmsClient getSmsClient(Long channelId);
|
||||
|
||||
/**
|
||||
* 获得短信 Client
|
||||
*
|
||||
* @param channelCode 渠道编码
|
||||
* @return 短信 Client
|
||||
*/
|
||||
SmsClient getSmsClient(String channelCode);
|
||||
|
||||
/**
|
||||
* 创建短信 Client
|
||||
*
|
||||
* @param properties 配置对象
|
||||
*/
|
||||
void createOrUpdateSmsClient(SmsChannelProperties properties);
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 消息接收 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class SmsReceiveRespDTO {
|
||||
|
||||
/**
|
||||
* 是否接收成功
|
||||
*/
|
||||
private Boolean success;
|
||||
/**
|
||||
* API 接收结果的编码
|
||||
*/
|
||||
private String errorCode;
|
||||
/**
|
||||
* API 接收结果的说明
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String mobile;
|
||||
/**
|
||||
* 用户接收时间
|
||||
*/
|
||||
private LocalDateTime receiveTime;
|
||||
|
||||
/**
|
||||
* 短信 API 发送返回的序号
|
||||
*/
|
||||
private String serialNo;
|
||||
/**
|
||||
* 短信日志编号
|
||||
*
|
||||
* 对应 SysSmsLogDO 的编号
|
||||
*/
|
||||
private Long logId;
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 短信发送 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class SmsSendRespDTO {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* API 请求编号
|
||||
*/
|
||||
private String apiRequestId;
|
||||
|
||||
// ==================== 成功时字段 ====================
|
||||
|
||||
/**
|
||||
* 短信 API 发送返回的序号
|
||||
*/
|
||||
private String serialNo;
|
||||
|
||||
// ==================== 失败时字段 ====================
|
||||
|
||||
/**
|
||||
* API 返回错误码
|
||||
*
|
||||
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||
*/
|
||||
private String apiCode;
|
||||
/**
|
||||
* API 返回提示
|
||||
*/
|
||||
private String apiMsg;
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 短信模板 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class SmsTemplateRespDTO {
|
||||
|
||||
/**
|
||||
* 模板编号
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 短信内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 审核状态
|
||||
*
|
||||
* 枚举 {@link SmsTemplateAuditStatusEnum}
|
||||
*/
|
||||
private Integer auditStatus;
|
||||
/**
|
||||
* 审核未通过的理由
|
||||
*/
|
||||
private String auditReason;
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 短信客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/2/1 9:28
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractSmsClient implements SmsClient {
|
||||
|
||||
/**
|
||||
* 短信渠道配置
|
||||
*/
|
||||
protected volatile SmsChannelProperties properties;
|
||||
|
||||
public AbstractSmsClient(SmsChannelProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public final void init() {
|
||||
doInit();
|
||||
log.debug("[init][配置({}) 初始化完成]", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义初始化
|
||||
*/
|
||||
protected abstract void doInit();
|
||||
|
||||
public final void refresh(SmsChannelProperties properties) {
|
||||
// 判断是否更新
|
||||
if (properties.equals(this.properties)) {
|
||||
return;
|
||||
}
|
||||
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
|
||||
this.properties = properties;
|
||||
// 初始化
|
||||
this.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return properties.getId();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 阿里短信客户端的实现类
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/1/25 14:17
|
||||
*/
|
||||
@Slf4j
|
||||
public class AliyunSmsClient extends AbstractSmsClient {
|
||||
|
||||
/**
|
||||
* 调用成功 code
|
||||
*/
|
||||
public static final String API_CODE_SUCCESS = "OK";
|
||||
|
||||
/**
|
||||
* REGION, 使用杭州
|
||||
*/
|
||||
private static final String ENDPOINT = "cn-hangzhou";
|
||||
|
||||
/**
|
||||
* 阿里云客户端
|
||||
*/
|
||||
private volatile IAcsClient client;
|
||||
|
||||
public AliyunSmsClient(SmsChannelProperties properties) {
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
|
||||
client = new DefaultAcsClient(profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
SendSmsRequest request = new SendSmsRequest();
|
||||
request.setPhoneNumbers(mobile);
|
||||
request.setSignName(properties.getSignature());
|
||||
request.setTemplateCode(apiTemplateId);
|
||||
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
||||
request.setOutId(String.valueOf(sendLogId));
|
||||
// 执行请求
|
||||
SendSmsResponse response = client.getAcsResponse(request);
|
||||
return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())
|
||||
.setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
|
||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
|
||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
|
||||
.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 构建请求
|
||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
||||
request.setTemplateCode(apiTemplateId);
|
||||
// 执行请求
|
||||
QuerySmsTemplateResponse response = client.getAcsResponse(request);
|
||||
if (response.getTemplateStatus() == null) {
|
||||
return null;
|
||||
}
|
||||
return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
|
||||
.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Integer convertSmsTemplateAuditStatus(Integer templateStatus) {
|
||||
switch (templateStatus) {
|
||||
case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
|
||||
case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
|
||||
case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
|
||||
default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信接收状态
|
||||
*
|
||||
* 参见 <a href="https://help.aliyun.com/document_detail/101867.html">文档</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public static class SmsReceiveStatus {
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@JsonProperty("phone_number")
|
||||
private String phoneNumber;
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
@JsonProperty("send_time")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private LocalDateTime sendTime;
|
||||
/**
|
||||
* 状态报告时间
|
||||
*/
|
||||
@JsonProperty("report_time")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private LocalDateTime reportTime;
|
||||
/**
|
||||
* 是否接收成功
|
||||
*/
|
||||
private Boolean success;
|
||||
/**
|
||||
* 状态报告说明
|
||||
*/
|
||||
@JsonProperty("err_msg")
|
||||
private String errMsg;
|
||||
/**
|
||||
* 状态报告编码
|
||||
*/
|
||||
@JsonProperty("err_code")
|
||||
private String errCode;
|
||||
/**
|
||||
* 发送序列号
|
||||
*/
|
||||
@JsonProperty("biz_id")
|
||||
private String bizId;
|
||||
/**
|
||||
* 用户序列号
|
||||
*
|
||||
* 这里我们传递的是 SysSmsLogDO 的日志编号
|
||||
*/
|
||||
@JsonProperty("out_id")
|
||||
private String outId;
|
||||
/**
|
||||
* 短信长度,例如说 1、2、3
|
||||
*
|
||||
* 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
|
||||
*/
|
||||
@JsonProperty("sms_size")
|
||||
private Integer smsSize;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于钉钉 WebHook 实现的调试的短信客户端实现类
|
||||
*
|
||||
* 考虑到省钱,我们使用钉钉 WebHook 模拟发送短信,方便调试。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DebugDingTalkSmsClient extends AbstractSmsClient {
|
||||
|
||||
public DebugDingTalkSmsClient(SmsChannelProperties properties) {
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
// 构建请求
|
||||
String url = buildUrl("robot/send");
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("msgtype", "text");
|
||||
String content = String.format("【模拟短信】\n手机号:%s\n短信日志编号:%d\n模板参数:%s",
|
||||
mobile, sendLogId, MapUtils.convertMap(templateParams));
|
||||
params.put("text", MapUtil.builder().put("content", content).build());
|
||||
// 执行请求
|
||||
String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));
|
||||
// 解析结果
|
||||
Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
|
||||
String errorCode = MapUtil.getStr(responseObj, "errcode");
|
||||
return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, "0")).setSerialNo(StrUtil.uuid())
|
||||
.setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, "errorMsg"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求地址
|
||||
*
|
||||
* 参见 <a href="https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71">文档</a>
|
||||
*
|
||||
* @param path 请求路径
|
||||
* @return 请求地址
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private String buildUrl(String path) {
|
||||
// 生成 timestamp
|
||||
long timestamp = System.currentTimeMillis();
|
||||
// 生成 sign
|
||||
String secret = properties.getApiSecret();
|
||||
String stringToSign = timestamp + "\n" + secret;
|
||||
byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign);
|
||||
String sign = Base64.encode(signData);
|
||||
// 构建最终 URL
|
||||
return String.format("https://oapi.dingtalk.com/%s?access_token=%s×tamp=%d&sign=%s",
|
||||
path, properties.getApiKey(), timestamp, sign);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||
throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) {
|
||||
return new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
|
||||
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 短信客户端工厂接口
|
||||
*
|
||||
* @author zzf
|
||||
*/
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class SmsClientFactoryImpl implements SmsClientFactory {
|
||||
|
||||
/**
|
||||
* 短信客户端 Map
|
||||
* key:渠道编号,使用 {@link SmsChannelProperties#getId()}
|
||||
*/
|
||||
private final ConcurrentMap<Long, AbstractSmsClient> channelIdClients = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 短信客户端 Map
|
||||
* key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()}
|
||||
*
|
||||
* 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
|
||||
* 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients}
|
||||
*/
|
||||
private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();
|
||||
|
||||
public SmsClientFactoryImpl() {
|
||||
// 初始化 channelCodeClients 集合
|
||||
Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
|
||||
// 创建一个空的 SmsChannelProperties 对象
|
||||
SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())
|
||||
.setApiKey("default default").setApiSecret("default");
|
||||
// 创建 Sms 客户端
|
||||
AbstractSmsClient smsClient = createSmsClient(properties);
|
||||
channelCodeClients.put(channel.getCode(), smsClient);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsClient getSmsClient(Long channelId) {
|
||||
return channelIdClients.get(channelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsClient getSmsClient(String channelCode) {
|
||||
return channelCodeClients.get(channelCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createOrUpdateSmsClient(SmsChannelProperties properties) {
|
||||
AbstractSmsClient client = channelIdClients.get(properties.getId());
|
||||
if (client == null) {
|
||||
client = this.createSmsClient(properties);
|
||||
client.init();
|
||||
channelIdClients.put(client.getId(), client);
|
||||
} else {
|
||||
client.refresh(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {
|
||||
SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode());
|
||||
Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
|
||||
// 创建客户端
|
||||
switch (channelEnum) {
|
||||
case ALIYUN: return new AliyunSmsClient(properties);
|
||||
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
|
||||
case TENCENT: return new TencentSmsClient(properties);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
|
||||
throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 腾讯云短信功能实现
|
||||
*
|
||||
* 参见 <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(SmsChannelProperties 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验腾讯云的 SDK AppId
|
||||
*
|
||||
* 原因是:腾讯云发放短信的时候,需要额外的参数 sdkAppId
|
||||
*
|
||||
* 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
*
|
||||
* @param properties 配置
|
||||
*/
|
||||
private static void validateSdkAppId(SmsChannelProperties 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 StrUtil.subAfter(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
private String getApiKey() {
|
||||
return StrUtil.subBefore(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||
String apiTemplateId, List<KeyValue<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());
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.enums;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 短信渠道枚举
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/1/25 10:56
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SmsChannelEnum {
|
||||
|
||||
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
|
||||
ALIYUN("ALIYUN", "阿里云"),
|
||||
TENCENT("TENCENT", "腾讯云"),
|
||||
// HUA_WEI("HUA_WEI", "华为云"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private final String code;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static SmsChannelEnum getByCode(String code) {
|
||||
return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 短信模板的审核状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum SmsTemplateAuditStatusEnum {
|
||||
|
||||
CHECKING(1),
|
||||
SUCCESS(2),
|
||||
FAIL(3);
|
||||
|
||||
private final Integer status;
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.property;
|
||||
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 短信渠道配置类
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021/1/25 17:01
|
||||
*/
|
||||
@Data
|
||||
@Validated
|
||||
public class SmsChannelProperties {
|
||||
|
||||
/**
|
||||
* 渠道编号
|
||||
*/
|
||||
@NotNull(message = "短信渠道 ID 不能为空")
|
||||
private Long id;
|
||||
/**
|
||||
* 短信签名
|
||||
*/
|
||||
@NotEmpty(message = "短信签名不能为空")
|
||||
private String signature;
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 {@link SmsChannelEnum}
|
||||
*/
|
||||
@NotEmpty(message = "渠道编码不能为空")
|
||||
private String code;
|
||||
/**
|
||||
* 短信 API 的账号
|
||||
*/
|
||||
@NotEmpty(message = "短信 API 的账号不能为空")
|
||||
private String apiKey;
|
||||
/**
|
||||
* 短信 API 的密钥
|
||||
*/
|
||||
@NotEmpty(message = "短信 API 的密钥不能为空")
|
||||
private String apiSecret;
|
||||
/**
|
||||
* 短信发送回调 URL
|
||||
*/
|
||||
private String callbackUrl;
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
|
@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.system.service.sms;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
|
@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.config.SmsCodeProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
|
@ -8,9 +8,9 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
|
@ -7,9 +7,9 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
|
@ -0,0 +1,187 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
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
|
||||
public void tesSendSms_success() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> 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<SendSmsRequest>) acsRequest -> {
|
||||
assertEquals(mobile, acsRequest.getPhoneNumbers());
|
||||
assertEquals(properties.getSignature(), acsRequest.getSignName());
|
||||
assertEquals(apiTemplateId, acsRequest.getTemplateCode());
|
||||
assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
|
||||
assertEquals(sendLogId.toString(), acsRequest.getOutId());
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertTrue(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getBizId(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tesSendSms_fail() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR"));
|
||||
when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
|
||||
assertEquals(mobile, acsRequest.getPhoneNumbers());
|
||||
assertEquals(properties.getSignature(), acsRequest.getSignName());
|
||||
assertEquals(apiTemplateId, acsRequest.getTemplateCode());
|
||||
assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
|
||||
assertEquals(sendLogId.toString(), acsRequest.getOutId());
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertFalse(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getCode(), result.getApiCode());
|
||||
assertEquals(response.getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getBizId(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSmsReceiveStatus() {
|
||||
// 准备参数
|
||||
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<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(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(LocalDateTime.of(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 testGetSmsTemplate() throws Throwable {
|
||||
// 准备参数
|
||||
String apiTemplateId = randomString();
|
||||
// mock 方法
|
||||
QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
|
||||
o.setCode("OK");
|
||||
o.setTemplateStatus(1); // 设置模板通过
|
||||
});
|
||||
when(client.getAcsResponse(argThat((ArgumentMatcher<QuerySmsTemplateRequest>) acsRequest -> {
|
||||
assertEquals(apiTemplateId, acsRequest.getTemplateCode());
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
|
||||
// 断言
|
||||
assertEquals(response.getTemplateCode(), result.getId());
|
||||
assertEquals(response.getTemplateContent(), result.getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||
assertEquals(response.getReason(), result.getAuditReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertSmsTemplateAuditStatus() {
|
||||
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)");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link TencentSmsClient} 的单元测试
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
||||
|
||||
private final SmsChannelProperties properties = new SmsChannelProperties()
|
||||
.setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
|
||||
.setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
|
||||
.setSignature("芋道源码");
|
||||
|
||||
@InjectMocks
|
||||
private TencentSmsClient smsClient = new TencentSmsClient(properties);
|
||||
|
||||
@Mock
|
||||
private SmsClient client;
|
||||
|
||||
@Test
|
||||
public void testDoInit() {
|
||||
// 准备参数
|
||||
// mock 方法
|
||||
|
||||
// 调用
|
||||
smsClient.doInit();
|
||||
// 断言
|
||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() {
|
||||
// 准备参数
|
||||
SmsChannelProperties p = new SmsChannelProperties()
|
||||
.setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
|
||||
.setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
|
||||
.setSignature("芋道源码");
|
||||
// 调用
|
||||
smsClient.refresh(p);
|
||||
// 断言
|
||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms_success() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
String requestId = randomString();
|
||||
String serialNo = randomString();
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||
o.setRequestId(requestId);
|
||||
SendStatus[] sendStatuses = new SendStatus[1];
|
||||
o.setSendStatusSet(sendStatuses);
|
||||
SendStatus sendStatus = new SendStatus();
|
||||
sendStatuses[0] = sendStatus;
|
||||
sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
|
||||
sendStatus.setMessage("send success");
|
||||
sendStatus.setSerialNo(serialNo);
|
||||
});
|
||||
when(client.SendSms(argThat(request -> {
|
||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||
assertEquals(properties.getSignature(), request.getSignName());
|
||||
assertEquals(apiTemplateId, request.getTemplateId());
|
||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||
toJsonString(request.getTemplateParamSet()));
|
||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertTrue(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms_fail() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
String requestId = randomString();
|
||||
String serialNo = randomString();
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||
o.setRequestId(requestId);
|
||||
SendStatus[] sendStatuses = new SendStatus[1];
|
||||
o.setSendStatusSet(sendStatuses);
|
||||
SendStatus sendStatus = new SendStatus();
|
||||
sendStatuses[0] = sendStatus;
|
||||
sendStatus.setCode("ERROR");
|
||||
sendStatus.setMessage("send success");
|
||||
sendStatus.setSerialNo(serialNo);
|
||||
});
|
||||
when(client.SendSms(argThat(request -> {
|
||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||
assertEquals(properties.getSignature(), request.getSignName());
|
||||
assertEquals(apiTemplateId, request.getTemplateId());
|
||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||
toJsonString(request.getTemplateParamSet()));
|
||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertFalse(result.getSuccess());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSmsReceiveStatus() {
|
||||
// 准备参数
|
||||
String text = "[\n" +
|
||||
" {\n" +
|
||||
" \"user_receive_time\": \"2015-10-17 08:03:04\",\n" +
|
||||
" \"nationcode\": \"86\",\n" +
|
||||
" \"mobile\": \"13900000001\",\n" +
|
||||
" \"report_status\": \"SUCCESS\",\n" +
|
||||
" \"errmsg\": \"DELIVRD\",\n" +
|
||||
" \"description\": \"用户短信送达成功\",\n" +
|
||||
" \"sid\": \"12345\",\n" +
|
||||
" \"ext\": {\"logId\":\"67890\"}\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
// mock 方法
|
||||
|
||||
// 调用
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);
|
||||
// 断言
|
||||
assertEquals(1, statuses.size());
|
||||
assertTrue(statuses.get(0).getSuccess());
|
||||
assertEquals("DELIVRD", statuses.get(0).getErrorCode());
|
||||
assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
|
||||
assertEquals("13900000001", statuses.get(0).getMobile());
|
||||
assertEquals(LocalDateTime.of(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
|
||||
assertEquals("12345", statuses.get(0).getSerialNo());
|
||||
assertEquals(67890L, statuses.get(0).getLogId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSmsTemplate() throws Throwable {
|
||||
// 准备参数
|
||||
Long apiTemplateId = randomLongId();
|
||||
String requestId = randomString();
|
||||
|
||||
// mock 方法
|
||||
DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
|
||||
DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setTemplateId(apiTemplateId);
|
||||
templateStatus.setStatusCode(0L);// 设置模板通过
|
||||
describeTemplateListStatuses[0] = templateStatus;
|
||||
o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
|
||||
o.setRequestId(requestId);
|
||||
});
|
||||
when(client.DescribeSmsTemplateList(argThat(request -> {
|
||||
assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
|
||||
// 断言
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertSmsTemplateAuditStatus() {
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(0));
|
||||
assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(1));
|
||||
assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
|
||||
smsClient.convertSmsTemplateAuditStatus(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),
|
||||
"未知审核状态(3)");
|
||||
}
|
||||
|
||||
}
|
@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.system.service.sms;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
|
||||
|
@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.config.SmsCodeProperties;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -4,9 +4,9 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
|
@ -5,9 +5,9 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
|
||||
|
Reference in New Issue
Block a user