短信提交 2021-03-28,增加发送日志
This commit is contained in:
@@ -15,9 +15,6 @@ import lombok.NoArgsConstructor;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SmsChannelPageReqVO extends PageParam {
|
||||
|
||||
@ApiModelProperty(value = "渠道名", example = "阿里", notes = "模糊匹配")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "签名值", example = "源码", notes = "模糊匹配")
|
||||
private String signature;
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package cn.iocoder.dashboard.modules.system.convert.sms;
|
||||
|
||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
|
||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||
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.resp.SmsChannelEnumRespVO;
|
||||
import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
|
||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,9 +17,6 @@ public interface SmsChannelConvert {
|
||||
|
||||
SmsChannelConvert INSTANCE = Mappers.getMapper(SmsChannelConvert.class);
|
||||
|
||||
@Mapping(source = "records", target = "list")
|
||||
PageResult<SysSmsChannelDO> convertPage(IPage<SysSmsChannelDO> page);
|
||||
|
||||
SysSmsChannelDO convert(SmsChannelCreateReqVO bean);
|
||||
|
||||
SysSmsChannelDO convert(SysUserUpdateReqVO bean);
|
||||
@@ -31,9 +25,8 @@ public interface SmsChannelConvert {
|
||||
|
||||
List<SmsChannelAllVO> convert(List<SysSmsChannelDO> bean);
|
||||
|
||||
List<SmsChannelProperty> convertProperty(List<SmsChannelAllVO> list);
|
||||
|
||||
List<SmsChannelProperty> convertProperties(List<SysSmsChannelDO> list);
|
||||
List<SmsChannelProperties> convertProperty(List<SmsChannelAllVO> list);
|
||||
|
||||
List<SmsChannelProperties> convertList(List<SysSmsChannelDO> list);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cn.iocoder.dashboard.modules.system.convert.sms;
|
||||
|
||||
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.dal.dataobject.sms.SysSmsChannelDO;
|
||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||
@@ -24,6 +23,4 @@ public interface SmsTemplateConvert {
|
||||
|
||||
SmsTemplateVO convert(SysSmsTemplateDO bean);
|
||||
|
||||
List<SmsTemplateProperty> convertProperty(List<SysSmsTemplateDO> bean);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,70 +1,58 @@
|
||||
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.sms.core.enums.SmsChannelEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
|
||||
/**
|
||||
* 短信渠道
|
||||
*
|
||||
* @author zzf
|
||||
* @since 2021-01-25
|
||||
*/
|
||||
@TableName(value = "sms_channel", autoResultMap = true)
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "sms_channel", autoResultMap = true)
|
||||
public class SysSmsChannelDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 自增编号
|
||||
* 渠道编号
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 {@link SmsChannelEnum}
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 启用状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
private Integer sendStatus;
|
||||
/**
|
||||
* 时间发送时间
|
||||
*/
|
||||
private Date sendTime;
|
||||
/**
|
||||
* 发送失败的类型
|
||||
*
|
||||
* 枚举 {@link SmsSendFailureTypeEnum}
|
||||
* 枚举 {@link SmsSendFailureTypeEnum#getType()}
|
||||
*/
|
||||
private Integer sendFailureType;
|
||||
/**
|
||||
* 发送成功时间
|
||||
* 发送失败的提示
|
||||
*
|
||||
* 一般情况下,使用 {@link SmsSendFailureTypeEnum#getMsg()}
|
||||
* 异常情况下,通过格式化 Exception 的提示存储
|
||||
*/
|
||||
private Date sendTime;
|
||||
private String sendFailureMsg;
|
||||
/**
|
||||
* 短信 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;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||
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.dal.dataobject.sms.SysSmsChannelDO;
|
||||
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 java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SysSmsChannelMapper extends BaseMapper<SysSmsChannelDO> {
|
||||
public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> {
|
||||
|
||||
default IPage<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
|
||||
return selectPage(MyBatisUtils.buildPage(reqVO), new LambdaQueryWrapper<SysSmsChannelDO>()
|
||||
.like(StrUtil.isNotBlank(reqVO.getName()), SysSmsChannelDO::getName, reqVO.getName())
|
||||
.like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getName, reqVO.getSignature())
|
||||
);
|
||||
default PageResult<SysSmsChannelDO> selectChannelPage(SmsChannelPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapper<SysSmsChannelDO>()
|
||||
.like(StrUtil.isNotBlank(reqVO.getSignature()), SysSmsChannelDO::getSignature, reqVO.getSignature()));
|
||||
}
|
||||
|
||||
default List<SysSmsChannelDO> selectEnabledList() {
|
||||
default List<SysSmsChannelDO> selectListByStatus(Integer status) {
|
||||
return selectList(new LambdaQueryWrapper<SysSmsChannelDO>()
|
||||
.eq(SysSmsChannelDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
|
||||
.orderByAsc(SysSmsChannelDO::getId)
|
||||
);
|
||||
.eq(SysSmsChannelDO::getStatus, status)
|
||||
.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;
|
||||
|
||||
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsSendLogDO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.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;
|
||||
|
||||
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.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.util.json.JsonUtils;
|
||||
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 javax.annotation.Resource;
|
||||
@@ -26,24 +18,9 @@ import javax.annotation.Resource;
|
||||
@Slf4j
|
||||
public class SmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
|
||||
|
||||
@Resource
|
||||
private SysSmsChannelService smsChannelService;
|
||||
|
||||
@Resource
|
||||
private SysSmsQueryLogService smsQueryLogService;
|
||||
|
||||
@Resource
|
||||
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
|
||||
public void onMessage(SysSmsSendMessage message) {
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package cn.iocoder.dashboard.modules.system.service.sms;
|
||||
|
||||
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.SmsChannelPageReqVO;
|
||||
import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
|
||||
@@ -46,26 +44,4 @@ public interface SysSmsChannelService {
|
||||
*/
|
||||
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;
|
||||
|
||||
import cn.iocoder.dashboard.framework.sms.core.enums.SmsSendFailureTypeEnum;
|
||||
import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -12,17 +13,38 @@ import java.util.Map;
|
||||
*/
|
||||
public interface SysSmsSendLogService {
|
||||
|
||||
/**
|
||||
* 创建发送日志
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param template 短信模板
|
||||
* @param templateContent 短信内容
|
||||
* @param templateParams 短信参数
|
||||
* @return
|
||||
*/
|
||||
Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
||||
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams);
|
||||
|
||||
/**
|
||||
* 更新发送日志为失败
|
||||
* 更新发送日志的结果
|
||||
*
|
||||
* @param id 发送日志编号
|
||||
* @param sendFailureType 失败类型
|
||||
* @param id 日志编号
|
||||
* @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;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.dashboard.common.pojo.PageResult;
|
||||
import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
|
||||
import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
|
||||
import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
|
||||
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.modules.system.controller.sms.vo.SmsChannelAllVO;
|
||||
import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
|
||||
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.resp.SmsChannelEnumRespVO;
|
||||
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.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 org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 短信渠道Service实现类
|
||||
@@ -37,10 +29,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Service
|
||||
public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||
|
||||
private final Map<String, Long> templateCode2ChannelIdMap = new ConcurrentHashMap<>(32);
|
||||
|
||||
@Resource
|
||||
private SmsClientFactory clientFactory;
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Resource
|
||||
private SysSmsChannelMapper channelMapper;
|
||||
@@ -48,30 +38,19 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||
@Resource
|
||||
private SysSmsTemplateMapper templateMapper;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initSmsClientAndCacheSmsTemplate() {
|
||||
// 查询有效渠道信息
|
||||
List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
|
||||
List<SmsChannelProperty> propertyList = SmsChannelConvert.INSTANCE.convertProperties(channelDOList);
|
||||
|
||||
// 遍历渠道生成client、获取模板并缓存
|
||||
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);
|
||||
}
|
||||
});
|
||||
List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
// 创建渠道 Client
|
||||
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList(channelDOList);
|
||||
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<SysSmsChannelDO> pageSmsChannels(SmsChannelPageReqVO reqVO) {
|
||||
return SmsChannelConvert.INSTANCE.convertPage(channelMapper.selectChannelPage(reqVO));
|
||||
return channelMapper.selectChannelPage(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,30 +65,20 @@ public class SysSmsChannelServiceImpl implements SysSmsChannelService {
|
||||
return SmsChannelConvert.INSTANCE.convertEnum(Arrays.asList(SmsChannelEnum.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSmsClient getSmsClient(String templateCode) {
|
||||
return clientFactory.getClient(templateCode2ChannelIdMap.get(templateCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSmsTemplateApiIdByCode(String templateCode) {
|
||||
return clientFactory.getTemplateApiIdByCode(templateCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
|
||||
List<SysSmsChannelDO> channelDOList = channelMapper.selectEnabledList();
|
||||
if (ObjectUtil.isNull(channelDOList)) {
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// @Override
|
||||
// public List<SmsChannelAllVO> listSmsChannelAllEnabledInfo() {
|
||||
// List<SysSmsChannelDO> channelDOList = channelMapper.selectListByStatus();
|
||||
// if (ObjectUtil.isNull(channelDOList)) {
|
||||
// return null;
|
||||
// }
|
||||
// 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;
|
||||
|
||||
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.SysSmsSendLogDOX;
|
||||
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.enums.sms.SysSmsSendStatusEnum;
|
||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
|
||||
import cn.iocoder.dashboard.modules.system.service.sms.SysSmsSendLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 短信发送日志服务实现类
|
||||
@@ -28,20 +23,9 @@ import java.util.Map;
|
||||
@Service
|
||||
public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
|
||||
|
||||
@Resource
|
||||
private SysSmsQueryLogMapper smsQueryLogMapper;
|
||||
|
||||
@Resource
|
||||
private SysSmsSendLogMapper smsSendLogMapper;
|
||||
|
||||
@Resource
|
||||
private SysSmsChannelService smsChannelService;
|
||||
|
||||
/**
|
||||
* 定时执行 {@link #getSmsSendResultJob()} 的周期
|
||||
*/
|
||||
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
|
||||
|
||||
@Override
|
||||
public Long createSmsSendLog(String mobile, Long userId, Integer userType,
|
||||
SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
|
||||
@@ -61,71 +45,12 @@ public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSmsSendLogFailure(Long id, Integer sendFailureType) {
|
||||
smsSendLogMapper.updateById(new SysSmsSendLogDO().setId(id).setSendFailureType(sendFailureType));
|
||||
public void updateSmsSendLogResult(Long id, Boolean success, Integer sendFailureType, String sendFailureMsg,
|
||||
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.iocoder.dashboard.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.dashboard.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
|
||||
import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
|
||||
import cn.iocoder.dashboard.framework.sms.core.SmsResult;
|
||||
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.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
|
||||
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.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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -31,29 +35,21 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
|
||||
* @date 2021/1/25 9:25
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SysSmsServiceImpl implements SysSmsService {
|
||||
|
||||
@Resource
|
||||
private SysSmsTemplateService smsTemplateService;
|
||||
@Resource
|
||||
private SysSmsSendLogService smsSendLogService;
|
||||
@Resource
|
||||
private SysSmsProducer smsProducer;
|
||||
@Resource
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Resource
|
||||
private SysUserService userService;
|
||||
|
||||
@Resource
|
||||
private SysSmsChannelService channelService;
|
||||
|
||||
|
||||
@Resource
|
||||
private SysSmsQueryLogService logService;
|
||||
|
||||
@Resource
|
||||
private SysSmsProducer smsProducer;
|
||||
|
||||
@Resource
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Override
|
||||
public void sendSingleSms(String mobile, Long userId, Integer userType,
|
||||
String templateCode, Map<String, Object> templateParams) {
|
||||
@@ -68,7 +64,7 @@ public class SysSmsServiceImpl implements SysSmsService {
|
||||
|
||||
// 如果模板被禁用,则直接标记发送失败。也就说,不发短信,嘿嘿。
|
||||
if (CommonStatusEnum.DISABLE.getStatus().equals(template.getStatus())) {
|
||||
smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE.getType());
|
||||
smsSendLogService.updateSmsSendLogFailure(sendLogId, SmsSendFailureTypeEnum.SMS_TEMPLATE_DISABLE);
|
||||
return;
|
||||
}
|
||||
// 如果模板未禁用,发送 MQ 消息。目的是,异步化调用短信平台
|
||||
@@ -126,19 +122,31 @@ public class SysSmsServiceImpl implements SysSmsService {
|
||||
SysUserDO user = userService.getUser(userId);
|
||||
return user != null ? user.getMobile() : null;
|
||||
}
|
||||
// TODO 芋艿:支持 C 端用户
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public Object smsSendCallbackHandle(ServletRequest request) {
|
||||
SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
|
||||
logService.updateSendLogByResultDetail(smsResultDetail);
|
||||
return smsResultDetail.getCallbackResponseBody();
|
||||
// SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
|
||||
// logService.updateSendLogByResultDetail(smsResultDetail);
|
||||
// return smsResultDetail.getCallbackResponseBody();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user