短信提交 2021-03-28,增加发送日志
parent
46ed64ba40
commit
515fca5c41
3
pom.xml
3
pom.xml
|
@ -46,6 +46,7 @@
|
||||||
<easyexcel.verion>2.2.7</easyexcel.verion>
|
<easyexcel.verion>2.2.7</easyexcel.verion>
|
||||||
<velocity.version>2.2</velocity.version>
|
<velocity.version>2.2</velocity.version>
|
||||||
<screw.version>1.0.5</screw.version>
|
<screw.version>1.0.5</screw.version>
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<!-- 依赖声明 -->
|
<!-- 依赖声明 -->
|
||||||
|
@ -271,6 +272,8 @@
|
||||||
<version>${screw.version}</version>
|
<version>${screw.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
|
|
||||||
<!-- SMS SDK begin -->
|
<!-- SMS SDK begin -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.yunpian.sdk</groupId>
|
<groupId>com.yunpian.sdk</groupId>
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.client;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 抽象短息客户端
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 2021/2/1 9:28
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public abstract class AbstractSmsClient implements SmsClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信渠道参数
|
|
||||||
*/
|
|
||||||
protected final SmsChannelProperty channelVO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信客户端有参构造函数
|
|
||||||
*
|
|
||||||
* @param property 短信配置
|
|
||||||
*/
|
|
||||||
public AbstractSmsClient(SmsChannelProperty property) {
|
|
||||||
this.channelVO = property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmsChannelProperty getProperty() {
|
|
||||||
return channelVO;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final SmsResult send(String templateApiId, SmsBody smsBody, String target) {
|
|
||||||
SmsResult result;
|
|
||||||
try {
|
|
||||||
beforeSend(templateApiId, smsBody, target);
|
|
||||||
result = doSend(templateApiId, smsBody, target);
|
|
||||||
afterSend(templateApiId, smsBody, target, result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// exception handle
|
|
||||||
log.debug(e.getMessage(), e);
|
|
||||||
return SmsResult.failResult("发送异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*
|
|
||||||
* @param templateApiId 短信模板唯一标识
|
|
||||||
* @param smsBody 消息内容
|
|
||||||
* @param targetPhone 发送对象手机号
|
|
||||||
* @return 短信发送结果
|
|
||||||
* @throws Exception 调用发送失败,抛出异常
|
|
||||||
*/
|
|
||||||
protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception;
|
|
||||||
|
|
||||||
protected void beforeSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterSend(String templateApiId, SmsBody smsBody, String targetPhone, SmsResult result) throws Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package cn.iocoder.dashboard.framework.sms.config;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.impl.SmsClientFactoryImpl;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SmsConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SmsClientFactory smsClientFactory() {
|
||||||
|
return new SmsClientFactoryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.core;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.util.json.JsonUtils;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息内容实体类
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class SmsBody {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息日志id
|
|
||||||
*/
|
|
||||||
private Long smsLogId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模板编码
|
|
||||||
*/
|
|
||||||
private String templateCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模板编码
|
|
||||||
*/
|
|
||||||
private String templateContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参数列表
|
|
||||||
*/
|
|
||||||
private Map<String, String> params;
|
|
||||||
|
|
||||||
public String getParamsStr() {
|
|
||||||
return JsonUtils.toJsonString(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.core;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.common.exception.ServiceException;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.impl.ali.AliyunSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.impl.yunpian.YunpianSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
|
|
||||||
import cn.iocoder.dashboard.util.json.JsonUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信客户端工厂
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 2021/1/28 14:01
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SmsClientFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* channelId: client map
|
|
||||||
* 保存 渠道id: 对应短信客户端 的map
|
|
||||||
*/
|
|
||||||
private final Map<Long, AbstractSmsClient> smsSenderMap = new ConcurrentHashMap<>(8);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* templateCode: TemplateProperty map
|
|
||||||
* 保存 模板编码:模板信息 的map
|
|
||||||
*/
|
|
||||||
private final Map<String, SmsTemplateProperty> templatePropertyMap = new ConcurrentHashMap<>(16);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建短信客户端
|
|
||||||
*
|
|
||||||
* @param propertyVO 参数对象
|
|
||||||
* @return 客户端id(默认channelId)
|
|
||||||
*/
|
|
||||||
public Long createClient(SmsChannelProperty propertyVO) {
|
|
||||||
AbstractSmsClient sender = createClient(SmsChannelEnum.getByCode(propertyVO.getCode()), propertyVO);
|
|
||||||
smsSenderMap.put(propertyVO.getId(), sender);
|
|
||||||
return propertyVO.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractSmsClient createClient(SmsChannelEnum channelEnum, SmsChannelProperty channelVO) {
|
|
||||||
if (channelEnum == null) {
|
|
||||||
throw new ServiceException(INVALID_CHANNEL_CODE);
|
|
||||||
}
|
|
||||||
switch (channelEnum) {
|
|
||||||
case ALIYUN:
|
|
||||||
return new AliyunSmsClient(channelVO);
|
|
||||||
case YUN_PIAN:
|
|
||||||
return new YunpianSmsClient(channelVO);
|
|
||||||
// TODO fill more channel
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new ServiceException(SMS_SENDER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取短信客户端
|
|
||||||
*
|
|
||||||
* @param channelId 渠道id
|
|
||||||
* @return 短信id
|
|
||||||
*/
|
|
||||||
public AbstractSmsClient getClient(Long channelId) {
|
|
||||||
return smsSenderMap.get(channelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加或修改短信模板信息缓存
|
|
||||||
*/
|
|
||||||
public void addOrUpdateTemplateCache(Collection<SmsTemplateProperty> templateProperties) {
|
|
||||||
templateProperties.forEach(this::addOrUpdateTemplateCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加或修改短信模板信息缓存
|
|
||||||
*/
|
|
||||||
public void addOrUpdateTemplateCache(SmsTemplateProperty templateProperty) {
|
|
||||||
templatePropertyMap.put(templateProperty.getCode(), templateProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据短信模板编码获取模板唯一标识
|
|
||||||
*
|
|
||||||
* @param templateCode 短信模板编码
|
|
||||||
* @return 短信id
|
|
||||||
*/
|
|
||||||
public String getTemplateApiIdByCode(String templateCode) {
|
|
||||||
SmsTemplateProperty smsTemplateProperty = templatePropertyMap.get(templateCode);
|
|
||||||
if (smsTemplateProperty == null) {
|
|
||||||
throw new ServiceException(SMS_TEMPLATE_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
return smsTemplateProperty.getApiTemplateId();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId
|
|
||||||
*
|
|
||||||
* @param callbackRequest 短信发送回调函数请求
|
|
||||||
* @return 第三方平台短信唯一标识
|
|
||||||
*/
|
|
||||||
public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {
|
|
||||||
for (Long channelId : smsSenderMap.keySet()) {
|
|
||||||
AbstractSmsClient smsClient = smsSenderMap.get(channelId);
|
|
||||||
try {
|
|
||||||
SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);
|
|
||||||
if (smsSendResult != null) {
|
|
||||||
return smsSendResult;
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "
|
|
||||||
+ JsonUtils.toJsonString(callbackRequest.getParameterMap()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,4 +15,5 @@ public interface SmsConstants {
|
||||||
String COMMA = ",";
|
String COMMA = ",";
|
||||||
|
|
||||||
String SUCCESS = "SUCCESS";
|
String SUCCESS = "SUCCESS";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.core;
|
package cn.iocoder.dashboard.framework.sms.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@ -9,33 +10,64 @@ import java.io.Serializable;
|
||||||
* 消息内容实体类
|
* 消息内容实体类
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
|
||||||
public class SmsResult implements Serializable {
|
public class SmsResult implements Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否成功(发送短信的请求是否成功)
|
* 是否成功
|
||||||
|
*
|
||||||
|
* 注意,是调用 API 短信平台的请求是否成功
|
||||||
*/
|
*/
|
||||||
private Boolean success;
|
private Boolean success;
|
||||||
|
/**
|
||||||
|
* 发送失败的类型
|
||||||
|
*
|
||||||
|
* 枚举 {@link SmsSendFailureTypeEnum#getType()}
|
||||||
|
*/
|
||||||
|
private Integer sendFailureType;
|
||||||
|
/**
|
||||||
|
* 发送失败的提示
|
||||||
|
*
|
||||||
|
* 一般情况下,使用 {@link SmsSendFailureTypeEnum#getMsg()}
|
||||||
|
* 异常情况下,通过格式化 Exception 的提示存储
|
||||||
|
*/
|
||||||
|
private String sendFailureMsg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 第三方唯一标识
|
* 短信 API 发送的错误码
|
||||||
|
*
|
||||||
|
* 由于第三方的错误码可能是字符串,所以使用 String 类型
|
||||||
*/
|
*/
|
||||||
private String apiId;
|
private String apiSendCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态码
|
* 短信 API 发送的提示
|
||||||
*/
|
*/
|
||||||
private String code;
|
private String apiSendMsg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提示
|
* 短信 API 发送返回的唯一请求 ID
|
||||||
|
*
|
||||||
|
* 用于和短信 API 进行定位于排错
|
||||||
*/
|
*/
|
||||||
private String message;
|
private String apiRequestId;
|
||||||
|
/**
|
||||||
|
* 短信 API 发送返回的序号
|
||||||
|
*
|
||||||
|
* 用于和短信 API 平台的发送记录关联
|
||||||
|
*/
|
||||||
|
private String apiSerialNo;
|
||||||
|
|
||||||
public static SmsResult failResult(String message) {
|
private SmsResult() {
|
||||||
SmsResult resultBody = new SmsResult();
|
|
||||||
resultBody.setSuccess(false);
|
|
||||||
resultBody.setMessage(message);
|
|
||||||
return resultBody;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SmsResult success(SmsSendFailureTypeEnum sendFailureType,
|
||||||
|
String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo) {
|
||||||
|
return new SmsResult().setSuccess(true).setSendFailureType(sendFailureType.getType()).setSendFailureMsg(sendFailureType.getMsg())
|
||||||
|
.setApiSendCode(apiSendCode).setApiSendMsg(apiSendMsg).setApiRequestId(apiRequestId).setApiSerialNo(apiSerialNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SmsResult error(Throwable ex) {
|
||||||
|
return new SmsResult().setSuccess(false)
|
||||||
|
.setSendFailureType(SmsSendFailureTypeEnum.SMS_SEND_EXCEPTION.getType())
|
||||||
|
.setSendFailureMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.client;
|
package cn.iocoder.dashboard.framework.sms.core.client;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信父接口
|
* 短信客户端接口
|
||||||
*
|
*
|
||||||
* @author zzf
|
* @author zzf
|
||||||
* @date 2021/1/25 14:14
|
* @date 2021/1/25 14:14
|
||||||
*/
|
*/
|
||||||
public interface SmsClient {
|
public interface SmsClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得渠道编号
|
||||||
|
*
|
||||||
|
* @return 渠道编号
|
||||||
|
*/
|
||||||
|
Long getId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息
|
* 发送消息
|
||||||
*
|
*
|
||||||
* @param templateApiId 短信模板唯一标识
|
* @param sendLogId 发送日志编号
|
||||||
* @param smsBody 消息内容
|
* @param mobile 手机号
|
||||||
* @param targets 发送对象列表
|
* @param apiTemplateId 短信 API 的模板编号
|
||||||
|
* @param templateParams 短信模板参数
|
||||||
* @return 短信发送结果
|
* @return 短信发送结果
|
||||||
*/
|
*/
|
||||||
SmsResult send(String templateApiId, SmsBody smsBody, String targets);
|
SmsResult send(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams);
|
||||||
|
|
||||||
// TODO FROM 芋艿 to ZZF:是不是可以改成意图更明确的解析返回结果,例如说 parseXXXX
|
// TODO FROM 芋艿 to ZZF:是不是可以改成意图更明确的解析返回结果,例如说 parseXXXX
|
||||||
/**
|
/**
|
|
@ -0,0 +1,28 @@
|
||||||
|
package cn.iocoder.dashboard.framework.sms.core.client;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信客户端工厂接口
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/1/28 14:01
|
||||||
|
*/
|
||||||
|
public interface SmsClientFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得短信 Client
|
||||||
|
*
|
||||||
|
* @param channelId 渠道编号
|
||||||
|
* @return 短信 Client
|
||||||
|
*/
|
||||||
|
SmsClient getSmsClient(Long channelId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建短信 Client
|
||||||
|
*
|
||||||
|
* @param properties 配置对象
|
||||||
|
*/
|
||||||
|
void createOrUpdateSmsClient(SmsChannelProperties properties);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package cn.iocoder.dashboard.framework.sms.core.client.impl;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信客户端抽象类
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 2021/2/1 9:28
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractSmsClient implements SmsClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信渠道配置
|
||||||
|
*/
|
||||||
|
protected volatile SmsChannelProperties properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信客户端有参构造函数
|
||||||
|
*
|
||||||
|
* @param properties 短信配置
|
||||||
|
*/
|
||||||
|
public AbstractSmsClient(SmsChannelProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
public final void init() {
|
||||||
|
doInit();
|
||||||
|
log.info("[init][配置({}) 初始化完成]", properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void refresh(SmsChannelProperties properties) {
|
||||||
|
// 判断是否更新
|
||||||
|
if (!properties.equals(this.properties)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
|
||||||
|
this.properties = properties;
|
||||||
|
// 初始化
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义初始化
|
||||||
|
*/
|
||||||
|
protected abstract void doInit();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return properties.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final SmsResult send(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) {
|
||||||
|
SmsResult result;
|
||||||
|
try {
|
||||||
|
result = doSend(sendLogId, mobile, apiTemplateId, templateParams);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
// 打印异常日志
|
||||||
|
log.error("[send][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
|
||||||
|
sendLogId, mobile, apiTemplateId, templateParams, ex);
|
||||||
|
// 封装返回
|
||||||
|
return SmsResult.error(ex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
*
|
||||||
|
* @param sendLogId 发送日志编号
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param apiTemplateId 短信 API 的模板编号
|
||||||
|
* @param templateParams 短信模板参数
|
||||||
|
* @return 短信发送结果
|
||||||
|
*/
|
||||||
|
protected abstract SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams)
|
||||||
|
throws Throwable;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package cn.iocoder.dashboard.framework.sms.core.client.impl;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信客户端工厂接口
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
public class SmsClientFactoryImpl implements SmsClientFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信客户端 Map
|
||||||
|
* key:渠道编号,使用 {@link SmsChannelProperties#getId()}
|
||||||
|
*/
|
||||||
|
private final Map<Long, AbstractSmsClient> clients = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmsClient getSmsClient(Long channelId) {
|
||||||
|
return clients.get(channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createOrUpdateSmsClient(SmsChannelProperties properties) {
|
||||||
|
AbstractSmsClient client = clients.get(properties.getId());
|
||||||
|
if (client == null) {
|
||||||
|
client = this.createSmsClient(properties);
|
||||||
|
clients.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 YUN_PIAN:
|
||||||
|
return new YunpianSmsClient(properties);
|
||||||
|
}
|
||||||
|
// 创建失败,错误日志 + 抛出异常
|
||||||
|
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
|
||||||
|
throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId
|
||||||
|
// *
|
||||||
|
// * @param callbackRequest 短信发送回调函数请求
|
||||||
|
// * @return 第三方平台短信唯一标识
|
||||||
|
// */
|
||||||
|
// public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {
|
||||||
|
// for (Long channelId : clients.keySet()) {
|
||||||
|
// AbstractSmsClient smsClient = clients.get(channelId);
|
||||||
|
// try {
|
||||||
|
// SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);
|
||||||
|
// if (smsSendResult != null) {
|
||||||
|
// return smsSendResult;
|
||||||
|
// }
|
||||||
|
// } catch (Exception ignored) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "
|
||||||
|
// + JsonUtils.toJsonString(callbackRequest.getParameterMap()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,15 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.client.impl.ali;
|
package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
||||||
import cn.iocoder.dashboard.util.json.JsonUtils;
|
import cn.iocoder.dashboard.util.json.JsonUtils;
|
||||||
import com.aliyuncs.DefaultAcsClient;
|
import com.aliyuncs.DefaultAcsClient;
|
||||||
import com.aliyuncs.IAcsClient;
|
import com.aliyuncs.IAcsClient;
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
|
|
||||||
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
|
||||||
import com.aliyuncs.http.MethodType;
|
|
||||||
import com.aliyuncs.profile.DefaultProfile;
|
import com.aliyuncs.profile.DefaultProfile;
|
||||||
import com.aliyuncs.profile.IClientProfile;
|
import com.aliyuncs.profile.IClientProfile;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
@ -28,7 +24,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里短信实现类
|
* 阿里短信客户端的实现类
|
||||||
*
|
*
|
||||||
* @author zzf
|
* @author zzf
|
||||||
* @date 2021/1/25 14:17
|
* @date 2021/1/25 14:17
|
||||||
|
@ -36,53 +32,53 @@ import java.util.Map;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AliyunSmsClient extends AbstractSmsClient {
|
public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
private static final String OK = "OK";
|
|
||||||
|
|
||||||
private static final String PRODUCT = "Dystopi";
|
private static final String PRODUCT = "Dystopi";
|
||||||
|
|
||||||
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
|
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
|
||||||
|
|
||||||
private static final String ENDPOINT = "cn-hangzhou";
|
private static final String ENDPOINT = "cn-hangzhou";
|
||||||
|
|
||||||
private final IAcsClient acsClient;
|
private static final String OK = "OK";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造阿里云短信发送处理
|
* 阿里云客户端
|
||||||
*
|
|
||||||
* @param channelVO 阿里云短信配置
|
|
||||||
*/
|
*/
|
||||||
public AliyunSmsClient(SmsChannelProperty channelVO) {
|
private volatile IAcsClient acsClient;
|
||||||
super(channelVO);
|
|
||||||
|
|
||||||
String accessKeyId = channelVO.getApiKey();
|
public AliyunSmsClient(SmsChannelProperties properties) {
|
||||||
String accessKeySecret = channelVO.getApiSecret();
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, accessKeyId, accessKeySecret);
|
@Override
|
||||||
|
protected void doInit() {
|
||||||
|
IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
|
||||||
DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
|
DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
|
||||||
|
|
||||||
acsClient = new DefaultAcsClient(profile);
|
acsClient = new DefaultAcsClient(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
|
protected SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) throws Exception {
|
||||||
SendSmsRequest request = new SendSmsRequest();
|
return null;
|
||||||
request.setSysMethod(MethodType.POST);
|
}
|
||||||
request.setPhoneNumbers(targetPhone);
|
|
||||||
request.setSignName(channelVO.getApiSignatureId());
|
|
||||||
request.setTemplateCode(templateApiId);
|
|
||||||
request.setTemplateParam(smsBody.getParamsStr());
|
|
||||||
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
|
|
||||||
|
|
||||||
boolean success = OK.equals(sendSmsResponse.getCode());
|
// @Override
|
||||||
if (!success) {
|
// public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
|
||||||
log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
|
// SendSmsRequest request = new SendSmsRequest();
|
||||||
}
|
// request.setSysMethod(MethodType.POST);
|
||||||
return new SmsResult()
|
// request.setPhoneNumbers(targetPhone);
|
||||||
.setSuccess(success)
|
// request.setSignName(properties.getSignature());
|
||||||
.setMessage(sendSmsResponse.getMessage())
|
// request.setTemplateCode(templateApiId);
|
||||||
.setCode(sendSmsResponse.getCode())
|
// request.setTemplateParam(smsBody.getParamsStr());
|
||||||
.setApiId(sendSmsResponse.getBizId());
|
// SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
|
||||||
}
|
//
|
||||||
|
// boolean success = OK.equals(sendSmsResponse.getCode());
|
||||||
|
// if (!success) {
|
||||||
|
// log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
|
||||||
|
// }
|
||||||
|
// return new SmsResult()
|
||||||
|
// .setSuccess(success)
|
||||||
|
// .setMessage(sendSmsResponse.getMessage())
|
||||||
|
// .setCode(sendSmsResponse.getCode())
|
||||||
|
// .setApiId(sendSmsResponse.getBizId());
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [{
|
* [{
|
||||||
|
@ -131,8 +127,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
public Integer getSendStatus() {
|
public Integer getSendStatus() {
|
||||||
return ((Boolean) sendResultParamMap.get(CallbackField.SUCCESS))
|
return ((Boolean) sendResultParamMap.get(CallbackField.SUCCESS))
|
||||||
? SysSmsSendStatusEnum.SEND_SUCCESS.getStatus()
|
? SysSmsSendStatusEnum.SUCCESS.getStatus()
|
||||||
: SysSmsSendStatusEnum.SEND_FAIL.getStatus();
|
: SysSmsSendStatusEnum.FAILURE.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBizId() {
|
public String getBizId() {
|
|
@ -1,19 +1,20 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.client.impl.yunpian;
|
package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
import cn.hutool.core.util.URLUtil;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsConstants;
|
import cn.iocoder.dashboard.framework.sms.core.SmsConstants;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
||||||
import cn.iocoder.dashboard.util.json.JsonUtils;
|
import cn.iocoder.dashboard.util.json.JsonUtils;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.yunpian.sdk.YunpianClient;
|
import com.yunpian.sdk.YunpianClient;
|
||||||
import com.yunpian.sdk.constant.Code;
|
|
||||||
import com.yunpian.sdk.constant.YunpianConstant;
|
import com.yunpian.sdk.constant.YunpianConstant;
|
||||||
import com.yunpian.sdk.model.Result;
|
import com.yunpian.sdk.model.Result;
|
||||||
import com.yunpian.sdk.model.SmsSingleSend;
|
import com.yunpian.sdk.model.SmsSingleSend;
|
||||||
|
@ -25,9 +26,10 @@ import java.net.URLEncoder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 云片短信实现类
|
* 云片短信客户端的实现类
|
||||||
*
|
*
|
||||||
* @author zzf
|
* @author zzf
|
||||||
* @date 9:48 2021/3/5
|
* @date 9:48 2021/3/5
|
||||||
|
@ -35,70 +37,58 @@ import java.util.Map;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class YunpianSmsClient extends AbstractSmsClient {
|
public class YunpianSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
private final YunpianClient client;
|
/**
|
||||||
|
* 云信短信客户端
|
||||||
|
*/
|
||||||
|
private volatile YunpianClient client;
|
||||||
|
|
||||||
private final TypeReference<List<Map<String, String>>> callbackType = new TypeReference<List<Map<String, String>>>() {
|
private final TypeReference<List<Map<String, String>>> callbackType = new TypeReference<List<Map<String, String>>>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
public YunpianSmsClient(SmsChannelProperties properties) {
|
||||||
* 构造云片短信发送处理
|
super(properties);
|
||||||
*
|
|
||||||
* @param channelVO 阿里云短信配置
|
|
||||||
*/
|
|
||||||
public YunpianSmsClient(SmsChannelProperty channelVO) {
|
|
||||||
super(channelVO);
|
|
||||||
client = new YunpianClient(channelVO.getApiKey());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) {
|
public void doInit() {
|
||||||
Map<String, String> paramMap = new HashMap<>();
|
client = new YunpianClient(properties.getApiKey());
|
||||||
paramMap.put(YunpianConstant.APIKEY, getProperty().getApiKey());
|
|
||||||
paramMap.put(YunpianConstant.MOBILE, String.join(SmsConstants.COMMA, targetPhone));
|
|
||||||
paramMap.put(YunpianConstant.TEXT, formatContent(smsBody));
|
|
||||||
paramMap.put(Helper.CALLBACK, getProperty().getCallbackUrl());
|
|
||||||
|
|
||||||
Result<SmsSingleSend> sendResult = client.sms().single_send(paramMap);
|
|
||||||
boolean success = sendResult.getCode().equals(Code.OK);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
log.debug("send fail[code={}, message={}]", sendResult.getCode(), sendResult.getDetail());
|
|
||||||
}
|
|
||||||
return new SmsResult()
|
|
||||||
.setSuccess(success)
|
|
||||||
.setMessage(sendResult.getDetail())
|
|
||||||
.setCode(sendResult.getCode().toString())
|
|
||||||
.setApiId(sendResult.getData().getSid().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SmsResult doSend(Long sendLogId, String mobile, String apiTemplateId, Map<String, Object> templateParams) throws Throwable {
|
||||||
|
// 构建参数
|
||||||
|
Map<String, String> request = new HashMap<>();
|
||||||
|
request.put(YunpianConstant.APIKEY, properties.getApiKey());
|
||||||
|
request.put(YunpianConstant.MOBILE, mobile);
|
||||||
|
request.put(YunpianConstant.TPL_ID, apiTemplateId);
|
||||||
|
request.put(YunpianConstant.TPL_VALUE, formatTplValue(templateParams));
|
||||||
|
request.put(YunpianConstant.UID, String.valueOf(sendLogId));
|
||||||
|
request.put(Helper.CALLBACK, properties.getCallbackUrl());
|
||||||
|
|
||||||
/**
|
// 执行发送
|
||||||
* 格式化短信内容,将参数注入到模板中
|
Result<SmsSingleSend> sendResult = client.sms().tpl_single_send(request);
|
||||||
*
|
if (sendResult.getThrowable() != null) {
|
||||||
* @param smsBody 短信信息
|
throw sendResult.getThrowable();
|
||||||
* @return 格式化后的短信内容
|
}
|
||||||
*/
|
// 解析结果
|
||||||
private String formatContent(SmsBody smsBody) {
|
SmsSingleSend data = sendResult.getData();
|
||||||
StringBuilder result = new StringBuilder(smsBody.getTemplateContent());
|
return SmsResult.success(parseSendFailureType(sendResult), // 将 API 短信平台,解析成统一的错误码
|
||||||
smsBody.getParams().forEach((key, val) -> {
|
String.valueOf(data.getCode()), data.getMsg(), null, String.valueOf(data.getSid()));
|
||||||
String param = parseParamToPlaceholder(key);
|
|
||||||
result.replace(result.indexOf(param), result.indexOf(param + param.length()), val);
|
|
||||||
});
|
|
||||||
return result.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String formatTplValue(Map<String, Object> templateParams) {
|
||||||
* 将指定参数改成对应的占位字符
|
if (CollUtil.isEmpty(templateParams)) {
|
||||||
* <p>
|
return "";
|
||||||
* 云片的是 #param# 的形式作为占位符
|
}
|
||||||
*
|
// 参考 https://www.yunpian.com/official/document/sms/zh_cn/introduction_demos_encode_sample 格式化
|
||||||
* @param key 参数名
|
StringJoiner joiner = new StringJoiner("&");
|
||||||
* @return 对应的占位字符
|
templateParams.forEach((key, value) -> joiner.add(String.format("#%s#=%s", key, URLUtil.encode(String.valueOf(value)))));
|
||||||
*/
|
return joiner.toString();
|
||||||
private String parseParamToPlaceholder(String key) {
|
|
||||||
return SmsConstants.JING_HAO + key + SmsConstants.JING_HAO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SmsSendFailureTypeEnum parseSendFailureType(Result<SmsSingleSend> sendResult) {
|
||||||
|
return SmsSendFailureTypeEnum.SMS_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 云片的比较复杂,又是加密又是套娃的
|
* 云片的比较复杂,又是加密又是套娃的
|
||||||
|
@ -109,7 +99,6 @@ public class YunpianSmsClient extends AbstractSmsClient {
|
||||||
return Helper.getSmsResultDetailByParam(map);
|
return Helper.getSmsResultDetailByParam(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 request 中获取请求中传入的短信发送结果信息
|
* 从 request 中获取请求中传入的短信发送结果信息
|
||||||
*
|
*
|
||||||
|
@ -155,8 +144,8 @@ public class YunpianSmsClient extends AbstractSmsClient {
|
||||||
private static int getSendStatus(Map<String, String> map) {
|
private static int getSendStatus(Map<String, String> map) {
|
||||||
String reportStatus = map.get(REPORT_STATUS);
|
String reportStatus = map.get(REPORT_STATUS);
|
||||||
return SmsConstants.SUCCESS.equals(reportStatus)
|
return SmsConstants.SUCCESS.equals(reportStatus)
|
||||||
? SysSmsSendStatusEnum.SEND_SUCCESS.getStatus()
|
? SysSmsSendStatusEnum.SUCCESS.getStatus()
|
||||||
: SysSmsSendStatusEnum.SEND_FAIL.getStatus();
|
: SysSmsSendStatusEnum.FAILURE.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SmsResultDetail getSmsResultDetailByParam(Map<String, String> map) {
|
public static SmsResultDetail getSmsResultDetailByParam(Map<String, String> map) {
|
|
@ -13,14 +13,23 @@ import lombok.Getter;
|
||||||
public enum SmsSendFailureTypeEnum {
|
public enum SmsSendFailureTypeEnum {
|
||||||
|
|
||||||
// ========== 模板相关(100 开头) ==========
|
// ========== 模板相关(100 开头) ==========
|
||||||
SMS_TEMPLATE_DISABLE(100), // 短信模板被禁用
|
SMS_CHANNEL_CLIENT_NOT_EXISTS(100, "短信渠道的客户端不存在"),
|
||||||
|
|
||||||
// ========== 其它相关 ==========
|
// ========== 模板相关(200 开头) ==========
|
||||||
|
SMS_TEMPLATE_DISABLE(200, "短信模板被禁用"),
|
||||||
|
|
||||||
|
// ========== 其它相关(900 开头) ==========
|
||||||
|
SMS_SEND_EXCEPTION(900, "发送异常"),
|
||||||
|
SMS_UNKNOWN(999, "未知错误,需要解析")
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 失败类型
|
* 失败类型
|
||||||
*/
|
*/
|
||||||
private final int type;
|
private final int type;
|
||||||
|
/**
|
||||||
|
* 失败提示
|
||||||
|
*/
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cn.iocoder.dashboard.framework.sms.core.property;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信渠道配置类
|
||||||
|
*
|
||||||
|
* @author zzf
|
||||||
|
* @date 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,68 +0,0 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.core.property;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道(包含模板)信息VO类
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 2021/1/25 17:01
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode
|
|
||||||
public class SmsChannelProperty implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id
|
|
||||||
*/
|
|
||||||
@NotNull(message = "短信渠道ID不能为空")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编码(来自枚举类 阿里、华为、七牛等)
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "短信渠道编码不能为空")
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道账号id
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "渠道账号id不能为空")
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道账号秘钥
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "渠道账号秘钥不能为空")
|
|
||||||
private String apiSecret;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实际渠道签名唯一标识
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "实际渠道签名唯一标识不能为空")
|
|
||||||
private String apiSignatureId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 签名值
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "签名值不能为空")
|
|
||||||
private String signature;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否拥有回调函数(0否 1是)
|
|
||||||
*/
|
|
||||||
@NotNull(message = "是否拥有回调函数不能为空")
|
|
||||||
private Integer hadCallback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信发送回调url
|
|
||||||
*/
|
|
||||||
private String callbackUrl;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package cn.iocoder.dashboard.framework.sms.core.property;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道模板VO类
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 2021/1/25 17:03
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode
|
|
||||||
public class SmsTemplateProperty {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道id
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "短信渠道编码不能为空")
|
|
||||||
private Long channelId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 业务编码(来自数据字典, 用户自定义业务场景 一个场景可以有多个模板)
|
|
||||||
*/
|
|
||||||
private String bizCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编码
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "短信模板编码不能为空")
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实际渠道模板唯一标识
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "短信模板唯一标识不能为空")
|
|
||||||
private String apiTemplateId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内容
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "短信模板内容不能为空")
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,9 +15,6 @@ import lombok.NoArgsConstructor;
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class SmsChannelPageReqVO extends PageParam {
|
public class SmsChannelPageReqVO extends PageParam {
|
||||||
|
|
||||||
@ApiModelProperty(value = "渠道名", example = "阿里", notes = "模糊匹配")
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "签名值", example = "源码", notes = "模糊匹配")
|
@ApiModelProperty(value = "签名值", example = "源码", notes = "模糊匹配")
|
||||||
private String signature;
|
private String signature;
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
package cn.iocoder.dashboard.modules.system.convert.sms;
|
package cn.iocoder.dashboard.modules.system.convert.sms;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -20,9 +17,6 @@ public interface SmsChannelConvert {
|
||||||
|
|
||||||
SmsChannelConvert INSTANCE = Mappers.getMapper(SmsChannelConvert.class);
|
SmsChannelConvert INSTANCE = Mappers.getMapper(SmsChannelConvert.class);
|
||||||
|
|
||||||
@Mapping(source = "records", target = "list")
|
|
||||||
PageResult<SysSmsChannelDO> convertPage(IPage<SysSmsChannelDO> page);
|
|
||||||
|
|
||||||
SysSmsChannelDO convert(SmsChannelCreateReqVO bean);
|
SysSmsChannelDO convert(SmsChannelCreateReqVO bean);
|
||||||
|
|
||||||
SysSmsChannelDO convert(SysUserUpdateReqVO bean);
|
SysSmsChannelDO convert(SysUserUpdateReqVO bean);
|
||||||
|
@ -31,9 +25,8 @@ public interface SmsChannelConvert {
|
||||||
|
|
||||||
List<SmsChannelAllVO> convert(List<SysSmsChannelDO> bean);
|
List<SmsChannelAllVO> convert(List<SysSmsChannelDO> bean);
|
||||||
|
|
||||||
List<SmsChannelProperty> convertProperty(List<SmsChannelAllVO> list);
|
List<SmsChannelProperties> convertProperty(List<SmsChannelAllVO> list);
|
||||||
|
|
||||||
List<SmsChannelProperty> convertProperties(List<SysSmsChannelDO> list);
|
|
||||||
|
|
||||||
|
List<SmsChannelProperties> convertList(List<SysSmsChannelDO> list);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cn.iocoder.dashboard.modules.system.convert.sms;
|
package cn.iocoder.dashboard.modules.system.convert.sms;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsTemplateVO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||||
|
@ -24,6 +23,4 @@ public interface SmsTemplateConvert {
|
||||||
|
|
||||||
SmsTemplateVO convert(SysSmsTemplateDO bean);
|
SmsTemplateVO convert(SysSmsTemplateDO bean);
|
||||||
|
|
||||||
List<SmsTemplateProperty> convertProperty(List<SysSmsTemplateDO> bean);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,58 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
|
package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信渠道
|
* 短信渠道
|
||||||
*
|
*
|
||||||
* @author zzf
|
* @author zzf
|
||||||
* @since 2021-01-25
|
* @since 2021-01-25
|
||||||
*/
|
*/
|
||||||
|
@TableName(value = "sms_channel", autoResultMap = true)
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName(value = "sms_channel", autoResultMap = true)
|
|
||||||
public class SysSmsChannelDO extends BaseDO {
|
public class SysSmsChannelDO extends BaseDO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自增编号
|
* 渠道编号
|
||||||
*/
|
*/
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编码(来自枚举类 阿里、华为、七牛等)
|
* 短信签名
|
||||||
*/
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信发送回调url
|
|
||||||
*/
|
|
||||||
private String callback_url;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道账号id
|
|
||||||
*/
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道账号秘钥
|
|
||||||
*/
|
|
||||||
private String apiSecret;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实际渠道签名唯一标识
|
|
||||||
*/
|
|
||||||
private String apiSignatureId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 签名值
|
|
||||||
*/
|
*/
|
||||||
private String signature;
|
private String signature;
|
||||||
|
/**
|
||||||
|
* 渠道编码
|
||||||
|
*
|
||||||
|
* 枚举 {@link SmsChannelEnum}
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*
|
||||||
|
* 枚举 {@link CommonStatusEnum}
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用状态(0正常 1停用)
|
* 短信 API 的账号
|
||||||
*/
|
*/
|
||||||
private Integer status;
|
private String apiKey;
|
||||||
|
/**
|
||||||
|
* 短信 API 的秘钥
|
||||||
|
*/
|
||||||
|
private String apiSecret;
|
||||||
|
/**
|
||||||
|
* 短信发送回调 URL
|
||||||
|
*/
|
||||||
|
private String callbackUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,16 +105,23 @@ public class SysSmsSendLogDO extends BaseDO {
|
||||||
* 枚举 {@link SysSmsSendStatusEnum}
|
* 枚举 {@link SysSmsSendStatusEnum}
|
||||||
*/
|
*/
|
||||||
private Integer sendStatus;
|
private Integer sendStatus;
|
||||||
|
/**
|
||||||
|
* 时间发送时间
|
||||||
|
*/
|
||||||
|
private Date sendTime;
|
||||||
/**
|
/**
|
||||||
* 发送失败的类型
|
* 发送失败的类型
|
||||||
*
|
*
|
||||||
* 枚举 {@link SmsSendFailureTypeEnum}
|
* 枚举 {@link SmsSendFailureTypeEnum#getType()}
|
||||||
*/
|
*/
|
||||||
private Integer sendFailureType;
|
private Integer sendFailureType;
|
||||||
/**
|
/**
|
||||||
* 发送成功时间
|
* 发送失败的提示
|
||||||
|
*
|
||||||
|
* 一般情况下,使用 {@link SmsSendFailureTypeEnum#getMsg()}
|
||||||
|
* 异常情况下,通过格式化 Exception 的提示存储
|
||||||
*/
|
*/
|
||||||
private Date sendTime;
|
private String sendFailureMsg;
|
||||||
/**
|
/**
|
||||||
* 短信 API 发送失败的类型
|
* 短信 API 发送失败的类型
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信日志
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @since 2021-01-25
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@TableName(value = "sms_send_log", autoResultMap = true)
|
|
||||||
public class SysSmsSendLogDOX implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自增编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信渠道编码(来自枚举类)
|
|
||||||
*/
|
|
||||||
private String channelCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信渠道id
|
|
||||||
*/
|
|
||||||
private Long channelId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模板id
|
|
||||||
*/
|
|
||||||
private String templateCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号
|
|
||||||
*/
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送状态
|
|
||||||
*
|
|
||||||
* @see SysSmsSendStatusEnum
|
|
||||||
*/
|
|
||||||
private Integer sendStatus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送时间
|
|
||||||
*/
|
|
||||||
private Date sendTime;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,31 +1,27 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
|
package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
|
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SysSmsChannelMapper extends BaseMapper<SysSmsChannelDO> {
|
public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> {
|
||||||
|
|
||||||
default IPage<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
|
default PageResult<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
|
||||||
return selectPage(MyBatisUtils.buildPage(reqVO), new LambdaQueryWrapper<SysSmsChannelDO>()
|
return selectPage(reqVO, new LambdaQueryWrapper<SysSmsChannelDO>()
|
||||||
.like(StrUtil.isNotBlank(reqVO.getName()), SysSmsChannelDO::getName, reqVO.getName())
|
.like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getSignature, reqVO.getSignature()));
|
||||||
.like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getName, reqVO.getSignature())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default List<SysSmsChannelDO> selectEnabledList() {
|
default List<SysSmsChannelDO> selectListByStatus(Integer status) {
|
||||||
return selectList(new LambdaQueryWrapper<SysSmsChannelDO>()
|
return selectList(new LambdaQueryWrapper<SysSmsChannelDO>()
|
||||||
.eq(SysSmsChannelDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
|
.eq(SysSmsChannelDO::getStatus, status)
|
||||||
.orderByAsc(SysSmsChannelDO::getId)
|
.orderByAsc(SysSmsChannelDO::getId));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.common.enums.DefaultBitFieldEnum;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Mapper
|
|
||||||
public interface SysSmsQueryLogMapper extends BaseMapper<SysSmsSendLogDO> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询还没有获取发送结果的短信请求信息
|
|
||||||
*/
|
|
||||||
default List<SysSmsSendLogDO> selectNoResultQueryLogList() {
|
|
||||||
return this.selectList(new LambdaQueryWrapper<SysSmsSendLogDO>()
|
|
||||||
.eq(SysSmsSendLogDO::getSendStatus, SysSmsSendStatusEnum.QUERY_SUCCESS)
|
|
||||||
.eq(SysSmsSendLogDO::getGotResult, DefaultBitFieldEnum.NO)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据APIId修改对象
|
|
||||||
*/
|
|
||||||
default boolean updateByApiId(SysSmsSendLogDO queryLogDO, String apiId) {
|
|
||||||
return update(queryLogDO, new LambdaQueryWrapper<SysSmsSendLogDO>()
|
|
||||||
.eq(SysSmsSendLogDO::getApiId, apiId)
|
|
||||||
) > 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,9 @@
|
||||||
package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
|
package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SysSmsSendLogMapper extends BaseMapper<SysSmsSendLogDO> {
|
public interface SysSmsSendLogMapper extends BaseMapperX<SysSmsSendLogDO> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
|
package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
|
import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
|
||||||
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
|
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
|
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
|
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
|
||||||
import cn.iocoder.dashboard.util.json.JsonUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
|
||||||
import org.springframework.data.redis.stream.StreamListener;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
@ -26,24 +18,9 @@ import javax.annotation.Resource;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
|
public class SmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsChannelService smsChannelService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsQueryLogService smsQueryLogService;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsService smsService;
|
private SysSmsService smsService;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessage(ObjectRecord<String, SmsSendMessage> record) {
|
|
||||||
AbstractSmsClient smsClient = smsChannelService.getSmsClient(body.getTemplateCode());
|
|
||||||
String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(body.getTemplateCode());
|
|
||||||
|
|
||||||
SmsResult result = smsClient.send(templateApiId, body, message.getTargetPhone());
|
|
||||||
smsQueryLogService.afterSendLog(body.getSmsLogId(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(SysSmsSendMessage message) {
|
public void onMessage(SysSmsSendMessage message) {
|
||||||
log.info("[onMessage][消息内容({})]", message);
|
log.info("[onMessage][消息内容({})]", message);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms;
|
package cn.iocoder.dashboard.modules.system.service.sms;
|
||||||
|
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
||||||
|
@ -46,26 +44,4 @@ public interface SysSmsChannelService {
|
||||||
*/
|
*/
|
||||||
List<SmsChannelEnumRespVO> getSmsChannelEnums();
|
List<SmsChannelEnumRespVO> getSmsChannelEnums();
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据短信模板编码获取短信客户端
|
|
||||||
*
|
|
||||||
* @param templateCode 短信模板编码
|
|
||||||
* @return 短信客户端
|
|
||||||
*/
|
|
||||||
AbstractSmsClient getSmsClient(String templateCode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据短信模板编码获取模板唯一标识
|
|
||||||
*
|
|
||||||
* @param templateCode 短信模板编码
|
|
||||||
* @return 短信客户端
|
|
||||||
*/
|
|
||||||
String getSmsTemplateApiIdByCode(String templateCode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询渠道(包含名下模块)信息集合
|
|
||||||
*
|
|
||||||
* @return 渠道(包含名下模块)信息集合
|
|
||||||
*/
|
|
||||||
List<SmsChannelAllVO> listSmsChannelAllEnabledInfo();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信请求日志服务接口
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 2021/1/25 9:24
|
|
||||||
*/
|
|
||||||
public interface SysSmsQueryLogService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送短信前的日志处理
|
|
||||||
*
|
|
||||||
* @param smsBody 短信内容
|
|
||||||
* @param targetPhone 发送对象手机号
|
|
||||||
* @param client 短信客户端
|
|
||||||
* @return 生成的日志id
|
|
||||||
*/
|
|
||||||
void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息后的日志处理
|
|
||||||
*
|
|
||||||
* @param logId 日志id
|
|
||||||
* @param result 消息结果
|
|
||||||
*/
|
|
||||||
void afterSendLog(Long logId, SmsResult result);
|
|
||||||
|
|
||||||
void updateSendLogByResultDetail(SmsResultDetail smsResultDetail);
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms;
|
package cn.iocoder.dashboard.modules.system.service.sms;
|
||||||
|
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -12,17 +13,38 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public interface SysSmsSendLogService {
|
public interface SysSmsSendLogService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建发送日志
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param userId 用户编号
|
||||||
|
* @param userType 用户类型
|
||||||
|
* @param template 短信模板
|
||||||
|
* @param templateContent 短信内容
|
||||||
|
* @param templateParams 短信参数
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
||||||
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams);
|
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新发送日志为失败
|
* 更新发送日志的结果
|
||||||
*
|
*
|
||||||
* @param id 发送日志编号
|
* @param id 日志编号
|
||||||
* @param sendFailureType 失败类型
|
* @param success 是否成功
|
||||||
|
* @param sendFailureType 发送失败的类型
|
||||||
|
* @param sendFailureMsg 发送失败的提示
|
||||||
|
* @param apiSendFailureType 短信 API 发送失败的类型
|
||||||
|
* @param apiSendFailureMsg 短信 API 发送失败的提示
|
||||||
|
* @param apiRequestId 短信 API 发送返回的唯一请求 ID
|
||||||
|
* @param apiSerialNo 短信 API 发送返回的序号
|
||||||
*/
|
*/
|
||||||
void updateSmsSendLogFailure(Long id, Integer sendFailureType);
|
void updateSmsSendLogResult(Long id, Boolean success, Integer sendFailureType, String sendFailureMsg,
|
||||||
|
String apiSendFailureType, String apiSendFailureMsg, String apiRequestId, String apiSerialNo);
|
||||||
|
|
||||||
void getAndSaveSmsSendLog();
|
default void updateSmsSendLogFailure(Long id, SmsSendFailureTypeEnum sendFailureType) {
|
||||||
|
updateSmsSendLogResult(id, false, sendFailureType.getType(), sendFailureType.getMsg(),
|
||||||
|
null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,24 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms.impl;
|
package cn.iocoder.dashboard.modules.system.service.sms.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
|
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
|
||||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
||||||
import cn.iocoder.dashboard.modules.system.convert.sms.SmsChannelConvert;
|
import cn.iocoder.dashboard.modules.system.convert.sms.SmsChannelConvert;
|
||||||
import cn.iocoder.dashboard.modules.system.convert.sms.SmsTemplateConvert;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsChannelMapper;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsTemplateMapper;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper;
|
||||||
|
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信渠道Service实现类
|
* 短信渠道Service实现类
|
||||||
|
@ -37,10 +29,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
@Service
|
@Service
|
||||||
public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||||
|
|
||||||
private final Map<String, Long> templateCode2ChannelIdMap = new ConcurrentHashMap<>(32);
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SmsClientFactory clientFactory;
|
private SmsClientFactory smsClientFactory;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsChannelMapper channelMapper;
|
private SysSmsChannelMapper channelMapper;
|
||||||
|
@ -48,30 +38,19 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsTemplateMapper templateMapper;
|
private SysSmsTemplateMapper templateMapper;
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
@Override
|
@Override
|
||||||
|
@PostConstruct
|
||||||
public void initSmsClientAndCacheSmsTemplate() {
|
public void initSmsClientAndCacheSmsTemplate() {
|
||||||
// 查询有效渠道信息
|
// 查询有效渠道信息
|
||||||
List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
|
List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
List<SmsChannelProperty> propertyList = SmsChannelConvert.INSTANCE.convertProperties(channelDOList);
|
// 创建渠道 Client
|
||||||
|
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList(channelDOList);
|
||||||
// 遍历渠道生成client、获取模板并缓存
|
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
|
||||||
propertyList.forEach(channelProperty -> {
|
|
||||||
List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(channelProperty.getId());
|
|
||||||
if (ObjectUtil.isNotEmpty(templateDOList)) {
|
|
||||||
Long clientId = clientFactory.createClient(channelProperty);
|
|
||||||
templateDOList.forEach(template -> templateCode2ChannelIdMap.put(template.getCode(), clientId));
|
|
||||||
|
|
||||||
List<SmsTemplateProperty> templatePropertyList = SmsTemplateConvert.INSTANCE.convertProperty(templateDOList);
|
|
||||||
clientFactory.addOrUpdateTemplateCache(templatePropertyList);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<SysSmsChannelDO> pageSmsChannels(SmsChannelPageReqVO reqVO) {
|
public PageResult<SysSmsChannelDO> pageSmsChannels(SmsChannelPageReqVO reqVO) {
|
||||||
return SmsChannelConvert.INSTANCE.convertPage(channelMapper.selectChannelPage(reqVO));
|
return channelMapper.selectChannelPage(reqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,30 +65,20 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||||
return SmsChannelConvert.INSTANCE.convertEnum(Arrays.asList(SmsChannelEnum.values()));
|
return SmsChannelConvert.INSTANCE.convertEnum(Arrays.asList(SmsChannelEnum.values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public AbstractSmsClient getSmsClient(String templateCode) {
|
// public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
|
||||||
return clientFactory.getClient(templateCode2ChannelIdMap.get(templateCode));
|
// List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus();
|
||||||
}
|
// if (ObjectUtil.isNull(channelDOList)) {
|
||||||
|
// return null;
|
||||||
@Override
|
// }
|
||||||
public String getSmsTemplateApiIdByCode(String templateCode) {
|
// List<SmsChannelAllVO> channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList);
|
||||||
return clientFactory.getTemplateApiIdByCode(templateCode);
|
// channelAllVOList.forEach(smsChannelDO -> {
|
||||||
}
|
// List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(smsChannelDO.getId());
|
||||||
|
// if (ObjectUtil.isNull(templateDOList)) {
|
||||||
@Override
|
// templateDOList = new ArrayList<>();
|
||||||
public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
|
// }
|
||||||
List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
|
// smsChannelDO.setTemplateList(SmsTemplateConvert.INSTANCE.convert(templateDOList));
|
||||||
if (ObjectUtil.isNull(channelDOList)) {
|
// });
|
||||||
return null;
|
// return channelAllVOList;
|
||||||
}
|
// }
|
||||||
List<SmsChannelAllVO> channelAllVOList = SmsChannelConvert.INSTANCE.convert(channelDOList);
|
|
||||||
channelAllVOList.forEach(smsChannelDO -> {
|
|
||||||
List<SysSmsTemplateDO> templateDOList = templateMapper.selectListByChannelId(smsChannelDO.getId());
|
|
||||||
if (ObjectUtil.isNull(templateDOList)) {
|
|
||||||
templateDOList = new ArrayList<>();
|
|
||||||
}
|
|
||||||
smsChannelDO.setTemplateList(SmsTemplateConvert.INSTANCE.convert(templateDOList));
|
|
||||||
});
|
|
||||||
return channelAllVOList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms.impl;
|
|
||||||
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsBody;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信请求日志服务实现类
|
|
||||||
*
|
|
||||||
* @author zzf
|
|
||||||
* @date 13:50 2021/3/2
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsQueryLogMapper logMapper;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client) {
|
|
||||||
SysSmsSendLogDO smsLog = new SysSmsSendLogDO();
|
|
||||||
SmsChannelProperty property = client.getProperty();
|
|
||||||
|
|
||||||
smsLog.setChannelCode(property.getCode())
|
|
||||||
.setChannelId(property.getId())
|
|
||||||
.setTemplateCode(smsBody.getTemplateCode())
|
|
||||||
.setPhone(targetPhone)
|
|
||||||
.setContent(smsBody.getParams().toString());
|
|
||||||
|
|
||||||
smsLog.setSendStatus(SysSmsSendStatusEnum.ASYNC.getStatus());
|
|
||||||
logMapper.insert(smsLog);
|
|
||||||
smsBody.setSmsLogId(smsLog.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterSendLog(Long logId, SmsResult result) {
|
|
||||||
SysSmsSendLogDO smsLog = new SysSmsSendLogDO();
|
|
||||||
smsLog.setId(logId);
|
|
||||||
smsLog.setApiId(result.getApiId());
|
|
||||||
smsLog.setSendStatus(SysSmsSendStatusEnum.QUERY_FAIL.getStatus());
|
|
||||||
smsLog.setRemark(result.getCode() + ": " + result.getMessage());
|
|
||||||
logMapper.updateById(smsLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSendLogByResultDetail(SmsResultDetail smsResultDetail) {
|
|
||||||
SysSmsSendLogDO queryLogDO = new SysSmsSendLogDO();
|
|
||||||
queryLogDO.setSendStatus(smsResultDetail.getSendStatus());
|
|
||||||
queryLogDO.setSendTime(smsResultDetail.getSendTime());
|
|
||||||
queryLogDO.setRemark(smsResultDetail.getMessage());
|
|
||||||
logMapper.updateByApiId(queryLogDO, smsResultDetail.getApiId());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,22 +1,17 @@
|
||||||
package cn.iocoder.dashboard.modules.system.service.sms.impl;
|
package cn.iocoder.dashboard.modules.system.service.sms.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
|
||||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDOX;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsQueryLogMapper;
|
|
||||||
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsSendLogMapper;
|
import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsSendLogMapper;
|
||||||
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
|
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.List;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信发送日志服务实现类
|
* 短信发送日志服务实现类
|
||||||
|
@ -28,20 +23,9 @@ import java.util.Map;
|
||||||
@Service
|
@Service
|
||||||
public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
|
public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsQueryLogMapper smsQueryLogMapper;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsSendLogMapper smsSendLogMapper;
|
private SysSmsSendLogMapper smsSendLogMapper;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsChannelService smsChannelService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时执行 {@link #getSmsSendResultJob()} 的周期
|
|
||||||
*/
|
|
||||||
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
public Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
||||||
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
|
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
|
||||||
|
@ -61,71 +45,12 @@ public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSmsSendLogFailure(Long id, Integer sendFailureType) {
|
public void updateSmsSendLogResult(Long id, Boolean success, Integer sendFailureType, String sendFailureMsg,
|
||||||
smsSendLogMapper.updateById(new SysSmsSendLogDO().setId(id).setSendFailureType(sendFailureType));
|
String apiSendFailureType, String apiSendFailureMsg, String apiRequestId, String apiSerialNo) {
|
||||||
|
SysSmsSendStatusEnum sendStatus = Objects.equals(success, true) ? SysSmsSendStatusEnum.SUCCESS : SysSmsSendStatusEnum.FAILURE;
|
||||||
|
smsSendLogMapper.updateById(new SysSmsSendLogDO().setId(id).setSendStatus(sendStatus.getStatus()).setSendTime(new Date())
|
||||||
|
.setSendFailureType(sendFailureType).setSendFailureMsg(sendFailureMsg)
|
||||||
|
.setApiSendFailureType(apiSendFailureType).setApiSendFailureMsg(apiSendFailureMsg).setApiRequestId(apiRequestId).setApiSerialNo(apiSerialNo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void getAndSaveSmsSendLog() {
|
|
||||||
|
|
||||||
List<SysSmsSendLogDO> noResultQueryLogList = smsQueryLogMapper.selectNoResultQueryLogList();
|
|
||||||
|
|
||||||
if (CollectionUtil.isEmpty(noResultQueryLogList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//用于添加的发送日志对象
|
|
||||||
SysSmsSendLogDOX insertSendLog = new SysSmsSendLogDOX();
|
|
||||||
//用于修改状态的请求日志对象
|
|
||||||
SysSmsSendLogDO updateQueryLog = new SysSmsSendLogDO();
|
|
||||||
|
|
||||||
noResultQueryLogList.forEach(queryLog -> {
|
|
||||||
AbstractSmsClient smsClient = smsChannelService.getSmsClient(queryLog.getTemplateCode());
|
|
||||||
|
|
||||||
updateQueryLog.setId(queryLog.getId());
|
|
||||||
|
|
||||||
// 只处理实现了获取发送结果方法的短信客户端,理论上这里都是满足条件的,以防万一加个判断。
|
|
||||||
/*if (smsClient instanceof NeedQuerySendResultSmsClient) {
|
|
||||||
//初始化点字段值
|
|
||||||
queryLog2SendLong(insertSendLog, queryLog);
|
|
||||||
|
|
||||||
NeedQuerySendResultSmsClient querySendResultSmsClient = (NeedQuerySendResultSmsClient) smsClient;
|
|
||||||
try {
|
|
||||||
List<SmsResultDetail> smsSendResult = querySendResultSmsClient.getSmsSendResult(queryLog.getRemark());
|
|
||||||
smsSendResult.forEach(resultDetail -> {
|
|
||||||
insertSendLog.setPhone(resultDetail.getPhone());
|
|
||||||
insertSendLog.setSendStatus(resultDetail.getSendStatus());
|
|
||||||
insertSendLog.setSendTime(resultDetail.getSendTime());
|
|
||||||
insertSendLog.setRemark(resultDetail.getMessage());
|
|
||||||
smsSendLogMapper.insert(insertSendLog);
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
//exception handle
|
|
||||||
log.error("query send result fail, exception: " + e.getMessage());
|
|
||||||
|
|
||||||
updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
|
|
||||||
updateQueryLog.setRemark(e.getMessage());
|
|
||||||
smsQueryLogMapper.updateById(updateQueryLog);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//理论上这里都是满足条件的,以防万一加个判断。
|
|
||||||
updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
|
|
||||||
smsQueryLogMapper.updateById(updateQueryLog);
|
|
||||||
}*/
|
|
||||||
updateQueryLog.setSendStatus(SysSmsSendStatusEnum.SEND_SUCCESS.getStatus());
|
|
||||||
updateQueryLog.setRemark(String.format("日志(id = %s)对应的客户端没有继承NeedQuerySendResultSmsClient, 不能获取短信结果。", queryLog.getId()));
|
|
||||||
smsQueryLogMapper.updateById(updateQueryLog);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void queryLog2SendLong(SysSmsSendLogDOX insertSendLog, SysSmsSendLogDO queryLog) {
|
|
||||||
insertSendLog.setChannelCode(queryLog.getChannelCode());
|
|
||||||
insertSendLog.setChannelId(queryLog.getChannelId());
|
|
||||||
insertSendLog.setTemplateCode(queryLog.getTemplateCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
|
||||||
public void getSmsSendResultJob() {
|
|
||||||
getAndSaveSmsSendLog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,19 @@ import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.dashboard.common.enums.UserTypeEnum;
|
import cn.iocoder.dashboard.common.enums.UserTypeEnum;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
|
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
|
||||||
|
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
|
||||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
|
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
|
||||||
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
|
import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
|
||||||
import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
|
import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
|
||||||
import cn.iocoder.dashboard.modules.system.service.sms.*;
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
|
||||||
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
|
||||||
|
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
|
||||||
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
|
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
@ -31,29 +35,21 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
|
||||||
* @date 2021/1/25 9:25
|
* @date 2021/1/25 9:25
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class SysSmsServiceImpl implements SysSmsService {
|
public class SysSmsServiceImpl implements SysSmsService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsTemplateService smsTemplateService;
|
private SysSmsTemplateService smsTemplateService;
|
||||||
@Resource
|
@Resource
|
||||||
private SysSmsSendLogService smsSendLogService;
|
private SysSmsSendLogService smsSendLogService;
|
||||||
|
@Resource
|
||||||
|
private SysSmsProducer smsProducer;
|
||||||
|
@Resource
|
||||||
|
private SmsClientFactory smsClientFactory;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysUserService userService;
|
private SysUserService userService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsChannelService channelService;
|
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsQueryLogService logService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SysSmsProducer smsProducer;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SmsClientFactory smsClientFactory;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendSingleSms(String mobile, Long userId, Integer userType,
|
public void sendSingleSms(String mobile, Long userId, Integer userType,
|
||||||
String templateCode, Map<String, Object> templateParams) {
|
String templateCode, Map<String, Object> templateParams) {
|
||||||
|
@ -68,7 +64,7 @@ public class SysSmsServiceImpl implements SysSmsService {
|
||||||
|
|
||||||
// 如果模板被禁用,则直接标记发送失败。也就说,不发短信,嘿嘿。
|
// 如果模板被禁用,则直接标记发送失败。也就说,不发短信,嘿嘿。
|
||||||
if (CommonStatusEnum.DISABLE.getStatus().equals(template.getStatus())) {
|
if (CommonStatusEnum.DISABLE.getStatus().equals(template.getStatus())) {
|
||||||
smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE.getType());
|
smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 如果模板未禁用,发送 MQ 消息。目的是,异步化调用短信平台
|
// 如果模板未禁用,发送 MQ 消息。目的是,异步化调用短信平台
|
||||||
|
@ -126,19 +122,31 @@ public class SysSmsServiceImpl implements SysSmsService {
|
||||||
SysUserDO user = userService.getUser(userId);
|
SysUserDO user = userService.getUser(userId);
|
||||||
return user != null ? user.getMobile() : null;
|
return user != null ? user.getMobile() : null;
|
||||||
}
|
}
|
||||||
|
// TODO 芋艿:支持 C 端用户
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doSendSms(SysSmsSendMessage message) {
|
public void doSendSms(SysSmsSendMessage message) {
|
||||||
|
// 获得渠道对应的 SmsClient 客户端
|
||||||
|
SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
|
||||||
|
if (smsClient == null) {
|
||||||
|
log.error("[doSendSms][短信 message({}) 找不到对应的客户端]", message);
|
||||||
|
smsSendLogService.updateSmsSendLogFailure(message.getSendLogId(), SmsSendFailureTypeEnum.SMS_CHANNEL_CLIENT_NOT_EXISTS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
SmsResult sendResult = smsClient.send(message.getSendLogId(), message.getMobile(),
|
||||||
|
message.getApiTemplateId(), message.getTemplateParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object smsSendCallbackHandle(ServletRequest request) {
|
public Object smsSendCallbackHandle(ServletRequest request) {
|
||||||
SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
|
// SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
|
||||||
logService.updateSendLogByResultDetail(smsResultDetail);
|
// logService.updateSendLogByResultDetail(smsResultDetail);
|
||||||
return smsResultDetail.getCallbackResponseBody();
|
// return smsResultDetail.getCallbackResponseBody();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue