电信获取下行信息完善、补充消息重试队列、消息重试任务
This commit is contained in:
@@ -18,6 +18,5 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
public class PushServerApplication {
|
public class PushServerApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(PushServerApplication.class, args);
|
SpringApplication.run(PushServerApplication.class, args);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,11 @@ public interface SmsSender {
|
|||||||
* @return 发送结果
|
* @return 发送结果
|
||||||
*/
|
*/
|
||||||
boolean sendBatchSms(List<MessageRecordDO> messageList);
|
boolean sendBatchSms(List<MessageRecordDO> messageList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询模板信息
|
||||||
|
*
|
||||||
|
* @param templateIdentifier 模板唯一标识符,例如:模板id、模板code
|
||||||
|
*/
|
||||||
|
void queryTemplate(String templateIdentifier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.njcn.msgpush.module.push.client.sender.impl;
|
package com.njcn.msgpush.module.push.client.sender.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.aliyun.dm20151123.Client;
|
import com.aliyun.dm20151123.Client;
|
||||||
import com.aliyun.dm20151123.models.SingleSendMailRequest;
|
import com.aliyun.dm20151123.models.SingleSendMailRequest;
|
||||||
import com.aliyun.dm20151123.models.SingleSendMailResponse;
|
import com.aliyun.dm20151123.models.SingleSendMailResponse;
|
||||||
@@ -9,13 +11,17 @@ import com.aliyun.teautil.models.RuntimeOptions;
|
|||||||
import com.njcn.msgpush.module.push.client.sender.EmailSender;
|
import com.njcn.msgpush.module.push.client.sender.EmailSender;
|
||||||
import com.njcn.msgpush.module.push.client.sender.Sender;
|
import com.njcn.msgpush.module.push.client.sender.Sender;
|
||||||
import com.njcn.msgpush.module.push.client.setting.impl.AliYunMailSetting;
|
import com.njcn.msgpush.module.push.client.setting.impl.AliYunMailSetting;
|
||||||
|
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
|
||||||
|
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import static com.aliyun.teautil.Common.toJSONString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author caozehui
|
* @author caozehui
|
||||||
@@ -28,6 +34,9 @@ public class AliyunEmailSender implements EmailSender {
|
|||||||
|
|
||||||
public static final String ACCOUNT_NAME = "accountName";
|
public static final String ACCOUNT_NAME = "accountName";
|
||||||
public static final String REPLY_TO_ADDRESS = "replyToAddress";
|
public static final String REPLY_TO_ADDRESS = "replyToAddress";
|
||||||
|
|
||||||
|
public static final String FROM_ALIAS = "fromAlias";
|
||||||
|
|
||||||
public static final String SUBJECT = "subject";
|
public static final String SUBJECT = "subject";
|
||||||
public static final String HTML_BODY = "htmlBody";
|
public static final String HTML_BODY = "htmlBody";
|
||||||
public static final String TEXT_BODY = "textBody";
|
public static final String TEXT_BODY = "textBody";
|
||||||
@@ -55,20 +64,40 @@ public class AliyunEmailSender implements EmailSender {
|
|||||||
public boolean sendEmail(MessageRecordDO message, Map<String, Object> params) {
|
public boolean sendEmail(MessageRecordDO message, Map<String, Object> params) {
|
||||||
RuntimeOptions runtimeOptions = new RuntimeOptions();
|
RuntimeOptions runtimeOptions = new RuntimeOptions();
|
||||||
runtimeOptions.autoretry = true;
|
runtimeOptions.autoretry = true;
|
||||||
|
|
||||||
|
String extraInfo = message.getExtraInfo();
|
||||||
|
JSONObject jsonObject = JSON.parseObject(extraInfo);
|
||||||
SingleSendMailRequest request = new SingleSendMailRequest()
|
SingleSendMailRequest request = new SingleSendMailRequest()
|
||||||
.setAccountName(params.get(ACCOUNT_NAME).toString())
|
.setAccountName(jsonObject.get(ACCOUNT_NAME).toString())
|
||||||
.setAddressType(1)
|
.setAddressType(1)
|
||||||
.setReplyToAddress((boolean) params.get(REPLY_TO_ADDRESS))
|
.setReplyToAddress((boolean) jsonObject.get(REPLY_TO_ADDRESS))
|
||||||
.setToAddress(message.getReceiver()) //目标地址,多个 email 地址可以用逗号分隔,最多 100 个地址(支持邮件组)。
|
.setToAddress(message.getReceiver()) //目标地址,多个 email 地址可以用逗号分隔,最多 100 个地址(支持邮件组)。
|
||||||
.setSubject(params.get(SUBJECT).toString())
|
.setSubject(message.getTitle())
|
||||||
.setHtmlBody(params.get(HTML_BODY).toString()) //HtmlBody 和 TextBody 是针对不同类型的邮件
|
.setHtmlBody(message.getContent()) //HtmlBody 和 TextBody 是针对不同类型的邮件
|
||||||
.setTextBody(params.get(TEXT_BODY).toString());
|
.setTextBody("")
|
||||||
|
.setFromAlias(jsonObject.get(FROM_ALIAS).toString());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
long start = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
message.setSendTime(now);
|
||||||
SingleSendMailResponse response = this.emailClient.singleSendMailWithOptions(request, runtimeOptions);
|
SingleSendMailResponse response = this.emailClient.singleSendMailWithOptions(request, runtimeOptions);
|
||||||
|
LocalDateTime end = LocalDateTime.now();
|
||||||
|
message.setCostTime((int) (end.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - start));
|
||||||
|
System.out.println(toJSONString(response));
|
||||||
if (HttpStatus.OK.value() == response.getStatusCode()) {
|
if (HttpStatus.OK.value() == response.getStatusCode()) {
|
||||||
|
this.sender.channelProviderConfigService.successUpdate(message.getProviderType(), message.getChannel());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
message.setErrorCode(response.getStatusCode() + "");
|
||||||
|
ProviderErrorCodeMappingDO providerErrorCode = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), response.getStatusCode() + "");
|
||||||
|
if (ObjectUtil.isNotNull(providerErrorCode)) {
|
||||||
|
message.setErrorMsg(providerErrorCode.getOriginalMessage());
|
||||||
|
} else {
|
||||||
|
message.setErrorMsg(MsgPushConstant.ERROR_MSG_UNKNOWN);
|
||||||
|
}
|
||||||
|
this.sender.messageRetryQueueService.saveOrUpdateRetryMessage(message);
|
||||||
|
this.sender.channelProviderConfigService.failureUpdate(message.getProviderType(), message.getChannel());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import com.njcn.msgpush.module.push.client.sender.Sender;
|
|||||||
import com.njcn.msgpush.module.push.client.sender.SmsSender;
|
import com.njcn.msgpush.module.push.client.sender.SmsSender;
|
||||||
import com.njcn.msgpush.module.push.client.setting.impl.AliYunMailSetting;
|
import com.njcn.msgpush.module.push.client.setting.impl.AliYunMailSetting;
|
||||||
import com.njcn.msgpush.module.push.constant.MessageStatusConstant;
|
import com.njcn.msgpush.module.push.constant.MessageStatusConstant;
|
||||||
|
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
@@ -40,11 +42,6 @@ public class AliyunSmsSender implements SmsSender {
|
|||||||
|
|
||||||
private Client smsClient;
|
private Client smsClient;
|
||||||
|
|
||||||
/**
|
|
||||||
* 存放发送完成的消息。key为其返回的mid
|
|
||||||
*/
|
|
||||||
// private Map<String, MessageRecordDO> completeSendMessageMap = new HashMap<>();
|
|
||||||
|
|
||||||
private ScheduledExecutorService scheduledExecutorService;
|
private ScheduledExecutorService scheduledExecutorService;
|
||||||
|
|
||||||
public AliyunSmsSender(AliYunMailSetting aliYunSmsSetting, Sender sender) {
|
public AliyunSmsSender(AliYunMailSetting aliYunSmsSetting, Sender sender) {
|
||||||
@@ -84,13 +81,14 @@ public class AliyunSmsSender implements SmsSender {
|
|||||||
LocalDateTime end = LocalDateTime.now();
|
LocalDateTime end = LocalDateTime.now();
|
||||||
message.setCostTime((int) (end.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - start));
|
message.setCostTime((int) (end.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - start));
|
||||||
System.out.println(toJSONString(response));
|
System.out.println(toJSONString(response));
|
||||||
if (OK.equals(response.body.code)) {
|
if (HttpStatus.OK.value() == response.getStatusCode()) {
|
||||||
this.getDownInfo(response.body.bizId, message);
|
this.getDownInfo(response.body.bizId, message);
|
||||||
|
this.sender.channelProviderConfigService.successUpdate(message.getProviderType(), message.getChannel());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
message.setErrorCode(response.body.code);
|
message.setErrorCode(response.body.code);
|
||||||
message.setErrorMsg(response.body.message);
|
message.setErrorMsg(response.body.message);
|
||||||
this.sender.messageRetryQueueService.addRetryMessage(message);
|
this.sender.messageRetryQueueService.saveOrUpdateRetryMessage(message);
|
||||||
this.sender.channelProviderConfigService.failureUpdate(message.getProviderType(), message.getChannel());
|
this.sender.channelProviderConfigService.failureUpdate(message.getProviderType(), message.getChannel());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -110,14 +108,19 @@ public class AliyunSmsSender implements SmsSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendBatchSms(List<MessageRecordDO> messageIdList) {
|
public boolean sendBatchSms(List<MessageRecordDO> messageList) {
|
||||||
boolean res = true;
|
boolean res = true;
|
||||||
for (MessageRecordDO message : messageIdList) {
|
for (MessageRecordDO message : messageList) {
|
||||||
res &= this.sendSms(message);
|
res &= this.sendSms(message);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queryTemplate(String templateIdentifier) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取下行信息
|
* 获取下行信息
|
||||||
*
|
*
|
||||||
@@ -143,8 +146,12 @@ public class AliyunSmsSender implements SmsSender {
|
|||||||
if (!"DELIVERED".equals(detail.errCode)) {
|
if (!"DELIVERED".equals(detail.errCode)) {
|
||||||
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), detail.errCode);
|
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), detail.errCode);
|
||||||
message.setErrorCode(detail.errCode);
|
message.setErrorCode(detail.errCode);
|
||||||
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
if (ObjectUtil.isNotNull(providerErrorCodeMappingDO)) {
|
||||||
this.sender.messageRetryQueueService.addRetryMessage(message);
|
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
||||||
|
} else {
|
||||||
|
message.setErrorMsg(MsgPushConstant.ERROR_MSG_UNKNOWN);
|
||||||
|
}
|
||||||
|
this.sender.messageRetryQueueService.saveOrUpdateRetryMessage(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.njcn.msgpush.module.push.client.sender.impl;
|
package com.njcn.msgpush.module.push.client.sender.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.njcn.msgpush.module.push.client.sender.Sender;
|
import com.njcn.msgpush.module.push.client.sender.Sender;
|
||||||
import com.njcn.msgpush.module.push.client.sender.SmsSender;
|
import com.njcn.msgpush.module.push.client.sender.SmsSender;
|
||||||
import com.njcn.msgpush.module.push.client.setting.impl.TelecomSmsSetting;
|
import com.njcn.msgpush.module.push.client.setting.impl.TelecomSmsSetting;
|
||||||
import com.njcn.msgpush.module.push.constant.MessageStatusConstant;
|
import com.njcn.msgpush.module.push.constant.MessageStatusConstant;
|
||||||
|
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -24,6 +27,8 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.alibaba.fastjson.JSON.toJSON;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author caozehui
|
* @author caozehui
|
||||||
* @data 2026-02-11
|
* @data 2026-02-11
|
||||||
@@ -85,12 +90,26 @@ public class TelecomSmsSender implements SmsSender {
|
|||||||
message.setStatus(MessageStatusConstant.SENDING);
|
message.setStatus(MessageStatusConstant.SENDING);
|
||||||
// 构建请求参数
|
// 构建请求参数
|
||||||
Map<String, Object> request = new HashMap<>();
|
Map<String, Object> request = new HashMap<>();
|
||||||
request.put("action", "send");
|
boolean isTemplateSend = StrUtil.isNotBlank(message.getTemplateCode());
|
||||||
request.put("account", telecomSmsSetting.getAccount());
|
request.put("account", telecomSmsSetting.getAccount());
|
||||||
request.put("password", telecomSmsSetting.getPassword());
|
request.put("password", telecomSmsSetting.getPassword());
|
||||||
request.put("mobile", message.getReceiver());
|
|
||||||
request.put("content", message.getContent());
|
|
||||||
request.put("extno", telecomSmsSetting.getExtno());
|
request.put("extno", telecomSmsSetting.getExtno());
|
||||||
|
if (isTemplateSend) {
|
||||||
|
request.put("action", "templatep2p");
|
||||||
|
Map<String, Object> templateJsonMap = new HashMap<>();
|
||||||
|
templateJsonMap.put("templateID", message.getTemplateCode());
|
||||||
|
JSONObject jsonObject = JSON.parseObject(message.getTemplateParams());
|
||||||
|
Map<String, String> variable = new HashMap<>();
|
||||||
|
variable.put("mobile", message.getReceiver());
|
||||||
|
jsonObject.forEach((key, value) -> variable.put(key, value.toString()));
|
||||||
|
templateJsonMap.put("variable", "[" + toJSON(variable) + "]");
|
||||||
|
request.put("templateJson", toJSON(templateJsonMap));
|
||||||
|
} else {
|
||||||
|
request.put("action", "send");
|
||||||
|
request.put("mobile", message.getReceiver());
|
||||||
|
request.put("content", message.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
@@ -119,7 +138,7 @@ public class TelecomSmsSender implements SmsSender {
|
|||||||
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), telecomSmsSendResponse.list.get(0).result + "");
|
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), telecomSmsSendResponse.list.get(0).result + "");
|
||||||
message.setErrorCode(telecomSmsSendResponse.list.get(0).result + "");
|
message.setErrorCode(telecomSmsSendResponse.list.get(0).result + "");
|
||||||
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
||||||
this.sender.messageRetryQueueService.addRetryMessage(message);
|
this.sender.messageRetryQueueService.saveOrUpdateRetryMessage(message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -136,43 +155,71 @@ public class TelecomSmsSender implements SmsSender {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendBatchSms(List<MessageRecordDO> messageList) {
|
public boolean sendBatchSms(List<MessageRecordDO> messageList) {
|
||||||
|
// Map<String, Object> request = new HashMap<>();
|
||||||
|
// request.put("action", "p2p");
|
||||||
|
// request.put("account", telecomSmsSetting.getAccount());
|
||||||
|
// request.put("password", telecomSmsSetting.getPassword());
|
||||||
|
// Map<String, String> mobileContentKvp = new HashMap<>();
|
||||||
|
// for (MessageRecordDO message : messageList) {
|
||||||
|
// mobileContentKvp.put(message.getReceiver(), "【" + message.getTitle() + "】" + message.getContent());
|
||||||
|
// message.setStatus(MessageStatusConstant.SENDING);
|
||||||
|
// }
|
||||||
|
// request.put("mobileContentKvp", JSON.toJSONString(mobileContentKvp));
|
||||||
|
// request.put("extno", telecomSmsSetting.getExtno());
|
||||||
|
//
|
||||||
|
// // 设置请求头
|
||||||
|
// HttpHeaders headers = new HttpHeaders();
|
||||||
|
// headers.set("Content-Type", CONTENT_TYPE);
|
||||||
|
//
|
||||||
|
// LocalDateTime now = LocalDateTime.now();
|
||||||
|
// long start = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
//
|
||||||
|
// // 发送请求
|
||||||
|
// ResponseEntity<String> response = this.sender.restTemplateUtil.post(
|
||||||
|
// telecomSmsSetting.getApiUrl(),
|
||||||
|
// request,
|
||||||
|
// headers,
|
||||||
|
// String.class
|
||||||
|
// );
|
||||||
|
// LocalDateTime end = LocalDateTime.now();
|
||||||
|
// for (MessageRecordDO message : messageList) {
|
||||||
|
// message.setSendTime(now);
|
||||||
|
// message.setCostTime((int) (end.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - start));
|
||||||
|
// }
|
||||||
|
// TelecomSmsSendResponse telecomSmsSendResponse = JSON.parseObject(response.getBody(), TelecomSmsSendResponse.class);
|
||||||
|
// boolean res = true;
|
||||||
|
// for (TelecomSmsSendDetailRes telecomSmsSendDetailRes : telecomSmsSendResponse.list) {
|
||||||
|
// res &= telecomSmsSendDetailRes.result == 0;
|
||||||
|
//
|
||||||
|
// // 定时任务,指定时间间隔后获取下行信息
|
||||||
|
// //this.getDownInfo(telecomSmsSendDetailRes.getMid(), message);
|
||||||
|
// }
|
||||||
|
// return res;
|
||||||
|
|
||||||
|
boolean res = true;
|
||||||
|
for (MessageRecordDO message : messageList) {
|
||||||
|
res &= this.sendSms(message);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queryTemplate(String templateIdentifier) {
|
||||||
Map<String, Object> request = new HashMap<>();
|
Map<String, Object> request = new HashMap<>();
|
||||||
request.put("action", "p2p");
|
request.put("action", "templateSelect");
|
||||||
request.put("account", telecomSmsSetting.getAccount());
|
request.put("account", telecomSmsSetting.getAccount());
|
||||||
request.put("password", telecomSmsSetting.getPassword());
|
request.put("password", telecomSmsSetting.getPassword());
|
||||||
Map<String, String> mobileContentKvp = new HashMap<>();
|
request.put("templateJson", StrUtil.isBlank(templateIdentifier) ? 0 : templateIdentifier);
|
||||||
for (MessageRecordDO message : messageList) {
|
|
||||||
mobileContentKvp.put(message.getReceiver(), "【" + message.getTitle() + "】" + message.getContent());
|
|
||||||
message.setStatus(MessageStatusConstant.SENDING);
|
|
||||||
}
|
|
||||||
request.put("mobileContentKvp", JSON.toJSONString(mobileContentKvp));
|
|
||||||
request.put("extno", telecomSmsSetting.getExtno());
|
|
||||||
|
|
||||||
// 设置请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.set("Content-Type", CONTENT_TYPE);
|
headers.set("Content-Type", CONTENT_TYPE);
|
||||||
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
long start = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
||||||
|
|
||||||
// 发送请求
|
|
||||||
ResponseEntity<String> response = this.sender.restTemplateUtil.post(
|
ResponseEntity<String> response = this.sender.restTemplateUtil.post(
|
||||||
telecomSmsSetting.getApiUrl(),
|
telecomSmsSetting.getApiUrl(),
|
||||||
request,
|
request,
|
||||||
headers,
|
headers,
|
||||||
String.class
|
String.class
|
||||||
);
|
);
|
||||||
LocalDateTime end = LocalDateTime.now();
|
System.out.println(toJSON(response));
|
||||||
for (MessageRecordDO message : messageList) {
|
|
||||||
message.setSendTime(now);
|
|
||||||
message.setCostTime((int) (end.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - start));
|
|
||||||
}
|
|
||||||
TelecomSmsSendResponse telecomSmsSendResponse = JSON.parseObject(response.getBody(), TelecomSmsSendResponse.class);
|
|
||||||
boolean res = true;
|
|
||||||
for (TelecomSmsSendDetailRes telecomSmsSendDetailRes : telecomSmsSendResponse.list) {
|
|
||||||
res &= telecomSmsSendDetailRes.result == 0;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,8 +262,12 @@ public class TelecomSmsSender implements SmsSender {
|
|||||||
if (telecomSmsSelectDetailRes.getStatus() == 5) {
|
if (telecomSmsSelectDetailRes.getStatus() == 5) {
|
||||||
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), telecomSmsSelectDetailRes.getStat());
|
ProviderErrorCodeMappingDO providerErrorCodeMappingDO = this.sender.providerErrorCodeMappingService.getByProviderErrorCode(message.getProviderType(), message.getChannel(), telecomSmsSelectDetailRes.getStat());
|
||||||
message.setErrorCode(telecomSmsSelectDetailRes.getStat());
|
message.setErrorCode(telecomSmsSelectDetailRes.getStat());
|
||||||
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
if (ObjectUtil.isNotNull(providerErrorCodeMappingDO)) {
|
||||||
this.sender.messageRetryQueueService.addRetryMessage(message);
|
message.setErrorMsg(providerErrorCodeMappingDO.getOriginalMessage());
|
||||||
|
} else {
|
||||||
|
message.setErrorMsg(MsgPushConstant.ERROR_MSG_UNKNOWN);
|
||||||
|
}
|
||||||
|
this.sender.messageRetryQueueService.saveOrUpdateRetryMessage(message);
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
this.scheduledExecutorService.shutdown();
|
this.scheduledExecutorService.shutdown();
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ public class MsgPushConstant {
|
|||||||
public static final String CHANNEL_SMS = "sms";
|
public static final String CHANNEL_SMS = "sms";
|
||||||
public static final String CHANNEL_EMAIL = "email";
|
public static final String CHANNEL_EMAIL = "email";
|
||||||
public static final String CHANNEL_APP_PUSH = "app_push";
|
public static final String CHANNEL_APP_PUSH = "app_push";
|
||||||
|
|
||||||
|
public static final String ERROR_MSG_UNKNOWN = "未知错误";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.njcn.msgpush.module.push.controller.admin.message.vo;
|
package com.njcn.msgpush.module.push.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.RegexPool;
|
||||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,10 +31,12 @@ public class MessageRecordReqVO extends PageParam {
|
|||||||
|
|
||||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "verify_code/order_notify/marketing/system_notify")
|
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "verify_code/order_notify/marketing/system_notify")
|
||||||
@NotBlank(message = "消息类型不能为空")
|
@NotBlank(message = "消息类型不能为空")
|
||||||
|
// @InEnum(value = CommonStatusEnum.class,message = "消息类型错误")
|
||||||
private String messageType;
|
private String messageType;
|
||||||
|
|
||||||
@Schema(description = "接收者", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "接收者", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@NotBlank(message = "接收者不能为空")
|
@NotBlank(message = "接收者不能为空")
|
||||||
|
@Pattern(regexp = RegexPool.EMAIL + "|" + RegexPool.MOBILE, message = "必须是有效的邮箱或手机号格式")
|
||||||
private String receiver;
|
private String receiver;
|
||||||
|
|
||||||
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@@ -53,4 +57,7 @@ public class MessageRecordReqVO extends PageParam {
|
|||||||
|
|
||||||
@Schema(description = "第三方消息ID")
|
@Schema(description = "第三方消息ID")
|
||||||
private String thirdPartyId;
|
private String thirdPartyId;
|
||||||
|
|
||||||
|
@Schema(description = "额外信息")
|
||||||
|
private String extraInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
|||||||
* @description 消息重试队列表对应数据对象
|
* @description 消息重试队列表对应数据对象
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("message_retry_queue")
|
@TableName("push_message_retry_queue")
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class MessageRetryQueueDO extends BaseDO {
|
public class MessageRetryQueueDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,36 +20,4 @@ public interface MessageRetryQueueMapper extends BaseMapperX<MessageRetryQueueDO
|
|||||||
*/
|
*/
|
||||||
List<MessageRetryQueueDO> selectNeedRetryMessages(@Param("currentTime") LocalDateTime currentTime,
|
List<MessageRetryQueueDO> selectNeedRetryMessages(@Param("currentTime") LocalDateTime currentTime,
|
||||||
@Param("limit") int limit);
|
@Param("limit") int limit);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新重试信息
|
|
||||||
*
|
|
||||||
* @param messageId 消息ID
|
|
||||||
* @param retryCount 重试次数
|
|
||||||
* @param nextRetryTime 下次重试时间
|
|
||||||
* @param lastRetryTime 最后重试时间
|
|
||||||
* @param lastErrorMsg 最后错误信息
|
|
||||||
* @return 影响行数
|
|
||||||
*/
|
|
||||||
int updateRetryInfo(@Param("messageId") String messageId,
|
|
||||||
@Param("retryCount") Integer retryCount,
|
|
||||||
@Param("nextRetryTime") LocalDateTime nextRetryTime,
|
|
||||||
@Param("lastRetryTime") LocalDateTime lastRetryTime,
|
|
||||||
@Param("lastErrorMsg") String lastErrorMsg);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除成功的重试记录
|
|
||||||
*
|
|
||||||
* @param messageId 消息ID
|
|
||||||
* @return 影响行数
|
|
||||||
*/
|
|
||||||
int deleteByMessageId(@Param("messageId") String messageId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除多个消息ID的重试记录
|
|
||||||
*
|
|
||||||
* @param messageIds 消息ID列表
|
|
||||||
* @return 影响行数
|
|
||||||
*/
|
|
||||||
int deleteByMessageIds(@Param("messageIds") List<String> messageIds);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,34 +4,11 @@
|
|||||||
|
|
||||||
<select id="selectNeedRetryMessages" resultType="com.njcn.msgpush.module.push.dal.dataobject.retry.MessageRetryQueueDO">
|
<select id="selectNeedRetryMessages" resultType="com.njcn.msgpush.module.push.dal.dataobject.retry.MessageRetryQueueDO">
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM message_retry_queue
|
FROM push_message_retry_queue
|
||||||
WHERE next_retry_time <![CDATA[ <= ]]> #{currentTime} and deleted != 0
|
WHERE next_retry_time <![CDATA[ <= ]]> #{currentTime} AND deleted = 0
|
||||||
ORDER BY next_retry_time ASC
|
ORDER BY next_retry_time ASC
|
||||||
LIMIT #{limit}
|
LIMIT #{limit}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<update id="updateRetryInfo">
|
|
||||||
UPDATE message_retry_queue
|
|
||||||
SET retry_count = #{retryCount},
|
|
||||||
next_retry_time = #{nextRetryTime},
|
|
||||||
last_retry_time = #{lastRetryTime},
|
|
||||||
last_error_msg = #{lastErrorMsg},
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE message_id = #{messageId} and deleted != 0
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<delete id="deleteByMessageId">
|
|
||||||
DELETE
|
|
||||||
FROM message_retry_queue
|
|
||||||
WHERE message_id = #{messageId}
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
<delete id="deleteByMessageIds">
|
|
||||||
DELETE FROM message_retry_queue
|
|
||||||
WHERE message_id IN
|
|
||||||
<foreach collection="messageIds" item="messageId" open="(" separator="," close=")">
|
|
||||||
#{messageId}
|
|
||||||
</foreach>
|
|
||||||
</delete>
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class MessageRetryJob {
|
|||||||
*/
|
*/
|
||||||
@Scheduled(fixedRate = 3000)
|
@Scheduled(fixedRate = 3000)
|
||||||
public void processEmailRetryQueue() {
|
public void processEmailRetryQueue() {
|
||||||
|
log.info("开始处理邮件重试队列:{}", LocalDateTime.now());
|
||||||
messageRetryQueueService.processRetryBatch("email");
|
messageRetryQueueService.processRetryBatch("email");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,4 +30,6 @@ public interface ChannelProviderConfigService extends IService<ChannelProviderCo
|
|||||||
ChannelProviderConfigDO getByTypeAndChannel(String providerType, String channel);
|
ChannelProviderConfigDO getByTypeAndChannel(String providerType, String channel);
|
||||||
|
|
||||||
void failureUpdate(String providerType, String channel);
|
void failureUpdate(String providerType, String channel);
|
||||||
|
|
||||||
|
void successUpdate(String providerType, String channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,13 @@ public class ChannelProviderConfigServiceImpl extends ServiceImpl<ChannelProvide
|
|||||||
byTypeAndChannel.setHealthStatus(byTypeAndChannel.getFailureCount() > 5 ? 0 : 1);
|
byTypeAndChannel.setHealthStatus(byTypeAndChannel.getFailureCount() > 5 ? 0 : 1);
|
||||||
this.updateById(byTypeAndChannel);
|
this.updateById(byTypeAndChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void successUpdate(String providerType, String channel) {
|
||||||
|
ChannelProviderConfigDO byTypeAndChannel = this.getByTypeAndChannel(providerType, channel);
|
||||||
|
byTypeAndChannel.setFailureCount(0);
|
||||||
|
byTypeAndChannel.setLastFailureTime(null);
|
||||||
|
byTypeAndChannel.setHealthStatus(1);
|
||||||
|
this.updateById(byTypeAndChannel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,4 +53,12 @@ public interface MessageRecordService {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean updateStatus(String messageId, String status);
|
boolean updateStatus(String messageId, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新消息记录重试次数
|
||||||
|
*
|
||||||
|
* @param messageId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean updateRetryCount(String messageId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.njcn.msgpush.module.push.service.message;
|
package com.njcn.msgpush.module.push.service.message;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
@@ -66,22 +67,28 @@ public class MessageRecordServiceImpl extends ServiceImpl<MessageRecordMapper, M
|
|||||||
public boolean processSendMsg(MessageRecordDO messageRecordDO) {
|
public boolean processSendMsg(MessageRecordDO messageRecordDO) {
|
||||||
ChannelProviderConfigDO channelProviderConfigDO = channelProviderConfigService.getByTypeAndChannel(messageRecordDO.getProviderType(), messageRecordDO.getChannel());
|
ChannelProviderConfigDO channelProviderConfigDO = channelProviderConfigService.getByTypeAndChannel(messageRecordDO.getProviderType(), messageRecordDO.getChannel());
|
||||||
MessageProviderFactory messageProviderFactory = messageProviderFactoryMap.get(messageRecordDO.getProviderType());
|
MessageProviderFactory messageProviderFactory = messageProviderFactoryMap.get(messageRecordDO.getProviderType());
|
||||||
if (ObjectUtil.isNull(messageProviderFactory)) {
|
if (ObjectUtil.isNull(messageProviderFactory) || ObjectUtil.isNull(channelProviderConfigDO)) {
|
||||||
throw new RuntimeException("暂不支持该供应商或者该供应商未激活:" + messageRecordDO.getProviderType());
|
throw new RuntimeException("暂不支持该供应商或者该供应商未激活:" + messageRecordDO.getProviderType());
|
||||||
}
|
}
|
||||||
boolean sendResult = switch (messageRecordDO.getChannel()) {
|
boolean sendResult = switch (messageRecordDO.getChannel()) {
|
||||||
case MsgPushConstant.CHANNEL_SMS -> messageProviderFactory.createSmsSender(channelProviderConfigDO, sender).sendSms(messageRecordDO);
|
case MsgPushConstant.CHANNEL_SMS -> messageProviderFactory.createSmsSender(channelProviderConfigDO, sender).sendSms(messageRecordDO);
|
||||||
case MsgPushConstant.CHANNEL_EMAIL ->
|
case MsgPushConstant.CHANNEL_EMAIL -> {
|
||||||
messageProviderFactory.createEmailSender(channelProviderConfigDO, sender).sendEmail(messageRecordDO, new HashMap<>());
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
yield messageProviderFactory.createEmailSender(channelProviderConfigDO, sender).sendEmail(messageRecordDO, params);
|
||||||
|
}
|
||||||
case MsgPushConstant.CHANNEL_APP_PUSH -> messageProviderFactory.createAppPushSender(channelProviderConfigDO, sender).appPush(messageRecordDO);
|
case MsgPushConstant.CHANNEL_APP_PUSH -> messageProviderFactory.createAppPushSender(channelProviderConfigDO, sender).appPush(messageRecordDO);
|
||||||
default -> throw new RuntimeException("暂不支持该渠道:" + messageRecordDO.getChannel());
|
default -> throw new RuntimeException("暂不支持该渠道:" + messageRecordDO.getChannel());
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateStatus(messageRecordDO.getMessageId(), sendResult ? MessageStatusConstant.SUCCESS : MessageStatusConstant.FAILED);
|
|
||||||
|
|
||||||
MessageRetryHistoryDO messageRetryHistoryDO = BeanUtil.copyProperties(messageRecordDO, MessageRetryHistoryDO.class, "id");
|
MessageRetryHistoryDO messageRetryHistoryDO = BeanUtil.copyProperties(messageRecordDO, MessageRetryHistoryDO.class, "id");
|
||||||
|
if (sendResult) {
|
||||||
|
this.updateStatus(messageRecordDO.getMessageId(), MessageStatusConstant.SUCCESS);
|
||||||
|
} else {
|
||||||
|
this.updateStatus(messageRecordDO.getMessageId(), MessageStatusConstant.FAILED);
|
||||||
|
}
|
||||||
messageRetryHistoryDO.setRetrySequence(messageRetryHistoryService.getMaxRetrySequence(messageRecordDO.getMessageId()));
|
messageRetryHistoryDO.setRetrySequence(messageRetryHistoryService.getMaxRetrySequence(messageRecordDO.getMessageId()));
|
||||||
messageRetryHistoryService.add(messageRetryHistoryDO);
|
messageRetryHistoryService.add(messageRetryHistoryDO);
|
||||||
|
|
||||||
return sendResult;
|
return sendResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +98,13 @@ public class MessageRecordServiceImpl extends ServiceImpl<MessageRecordMapper, M
|
|||||||
return this.lambdaUpdate().eq(MessageRecordDO::getMessageId, messageId).set(MessageRecordDO::getStatus, status).update();
|
return this.lambdaUpdate().eq(MessageRecordDO::getMessageId, messageId).set(MessageRecordDO::getStatus, status).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateRetryCount(String messageId) {
|
||||||
|
MessageRecordDO one = this.lambdaQuery().eq(MessageRecordDO::getMessageId, messageId).one();
|
||||||
|
one.setRetryCount(one.getRetryCount() + 1);
|
||||||
|
return this.updateById(one);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MessageRecordDO getById(String messageId) {
|
public MessageRecordDO getById(String messageId) {
|
||||||
return this.lambdaQuery().eq(MessageRecordDO::getMessageId, messageId).eq(MessageRecordDO::getDeleted, true).one();
|
return this.lambdaQuery().eq(MessageRecordDO::getMessageId, messageId).eq(MessageRecordDO::getDeleted, true).one();
|
||||||
@@ -104,7 +118,7 @@ public class MessageRecordServiceImpl extends ServiceImpl<MessageRecordMapper, M
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MessageRecordDO> listByIds(Collection<? extends Serializable> ids) {
|
public List<MessageRecordDO> listByIds(Collection<? extends Serializable> ids) {
|
||||||
return this.baseMapper.selectByIds(ids);
|
return this.lambdaQuery().in(CollectionUtil.isNotEmpty(ids), MessageRecordDO::getMessageId, ids).list();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.njcn.msgpush.module.push.controller.admin.retry.vo.MessageRetryQueueR
|
|||||||
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
|
||||||
import com.njcn.msgpush.module.push.dal.dataobject.retry.MessageRetryQueueDO;
|
import com.njcn.msgpush.module.push.dal.dataobject.retry.MessageRetryQueueDO;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface MessageRetryQueueService {
|
public interface MessageRetryQueueService {
|
||||||
@@ -13,7 +14,7 @@ public interface MessageRetryQueueService {
|
|||||||
*
|
*
|
||||||
* @param message 消息
|
* @param message 消息
|
||||||
*/
|
*/
|
||||||
void addRetryMessage(MessageRecordDO message);
|
void saveOrUpdateRetryMessage(MessageRecordDO message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量处理重试消息
|
* 批量处理重试消息
|
||||||
@@ -30,13 +31,6 @@ public interface MessageRetryQueueService {
|
|||||||
*/
|
*/
|
||||||
void manualRetry(List<String> messageIds);
|
void manualRetry(List<String> messageIds);
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除重试记录
|
|
||||||
*
|
|
||||||
* @param messageId 消息ID
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
boolean removeRetryRecord(String messageId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询重试队列
|
* 分页查询重试队列
|
||||||
@@ -45,4 +39,17 @@ public interface MessageRetryQueueService {
|
|||||||
* @return 分页结果
|
* @return 分页结果
|
||||||
*/
|
*/
|
||||||
PageResult<MessageRetryQueueDO> getPage(MessageRetryQueueReqVO reqVO);
|
PageResult<MessageRetryQueueDO> getPage(MessageRetryQueueReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新重试信息
|
||||||
|
*
|
||||||
|
* @param messageId 消息ID
|
||||||
|
* @param retryCount 重试次数
|
||||||
|
* @param nextRetryTime 下次重试时间
|
||||||
|
* @param lastRetryTime 最后重试时间
|
||||||
|
* @param lastErrorMsg 最后错误信息
|
||||||
|
*/
|
||||||
|
boolean updateRetryInfo(String messageId, int retryCount, LocalDateTime nextRetryTime, LocalDateTime lastRetryTime, String lastErrorMsg);
|
||||||
|
|
||||||
|
boolean deleteByMessageIds(List<String> messageIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -40,6 +41,7 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
@Autowired
|
@Autowired
|
||||||
public RetryStrategyConfigService retryStrategyConfigService;
|
public RetryStrategyConfigService retryStrategyConfigService;
|
||||||
|
|
||||||
|
|
||||||
public final ThreadPoolExecutor MSG_RETRY_THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
|
public final ThreadPoolExecutor MSG_RETRY_THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
|
||||||
5,
|
5,
|
||||||
5,
|
5,
|
||||||
@@ -68,11 +70,11 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
/**
|
/**
|
||||||
* 默认最大重试次数
|
* 默认最大重试次数
|
||||||
*/
|
*/
|
||||||
private static final int DEFAULT_MAX_RETRY_COUNT = 3;
|
private static final int DEFAULT_MAX_RETRY_COUNT = 5;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void addRetryMessage(MessageRecordDO message) {
|
public void saveOrUpdateRetryMessage(MessageRecordDO message) {
|
||||||
// 检查是否已存在重试记录
|
// 检查是否已存在重试记录
|
||||||
MessageRetryQueueDO existing = super.baseMapper.selectOne(
|
MessageRetryQueueDO existing = super.baseMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageRetryQueueDO>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageRetryQueueDO>()
|
||||||
@@ -86,20 +88,30 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
retryRecord.setMessageId(message.getMessageId());
|
retryRecord.setMessageId(message.getMessageId());
|
||||||
retryRecord.setChannel(message.getChannel());
|
retryRecord.setChannel(message.getChannel());
|
||||||
retryRecord.setReceiver(message.getReceiver());
|
retryRecord.setReceiver(message.getReceiver());
|
||||||
retryRecord.setRetryCount(0);
|
retryRecord.setRetryCount(1);
|
||||||
retryRecord.setMaxRetry(message.getRetryCount());
|
RetryStrategyConfigDO strategyConfig = retryStrategyConfigService.getStrategyConfig(message.getChannel());
|
||||||
retryRecord.setFirstFailTime(LocalDateTime.now());
|
if (ObjectUtil.isNull(strategyConfig)) {
|
||||||
|
retryRecord.setMaxRetry(DEFAULT_MAX_RETRY_COUNT);
|
||||||
|
} else {
|
||||||
|
retryRecord.setMaxRetry(strategyConfig.getMaxRetryCount());
|
||||||
|
}
|
||||||
|
retryRecord.setFirstFailTime(message.getSendTime());
|
||||||
retryRecord.setLastErrorMsg(message.getErrorMsg());
|
retryRecord.setLastErrorMsg(message.getErrorMsg());
|
||||||
|
|
||||||
// 计算下次重试时间
|
// 计算下次重试时间
|
||||||
LocalDateTime nextRetryTime = this.calculateNextRetryTime(message.getChannel(), 1);
|
LocalDateTime nextRetryTime = this.calculateNextRetryTime(message.getChannel(), retryRecord.getRetryCount());
|
||||||
retryRecord.setNextRetryTime(nextRetryTime);
|
retryRecord.setNextRetryTime(nextRetryTime);
|
||||||
|
message.setNextRetryTime(nextRetryTime);
|
||||||
super.baseMapper.insert(retryRecord);
|
this.save(retryRecord);
|
||||||
|
} else {
|
||||||
// 同步到Redis
|
existing.setRetryCount(existing.getRetryCount() + 1);
|
||||||
messageRetryRedisDAO.addToRetryQueue(message);
|
// 计算下次重试时间
|
||||||
|
LocalDateTime nextRetryTime = this.calculateNextRetryTime(message.getChannel(), existing.getRetryCount());
|
||||||
|
existing.setNextRetryTime(nextRetryTime);
|
||||||
|
message.setNextRetryTime(nextRetryTime);
|
||||||
|
this.updateById(existing);
|
||||||
}
|
}
|
||||||
|
// 同步到Redis
|
||||||
|
messageRetryRedisDAO.addToRetryQueue(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +139,8 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
break;
|
break;
|
||||||
case MsgPushConstant.CHANNEL_EMAIL: {
|
case MsgPushConstant.CHANNEL_EMAIL: {
|
||||||
if (retryCount == 1) {
|
if (retryCount == 1) {
|
||||||
plusSeconds = 60 * 10;
|
// plusSeconds = 60 * 10;
|
||||||
|
plusSeconds = 60 * 2;
|
||||||
} else if (retryCount == 2) {
|
} else if (retryCount == 2) {
|
||||||
plusSeconds = 60 * 30;
|
plusSeconds = 60 * 30;
|
||||||
} else if (retryCount == 3) {
|
} else if (retryCount == 3) {
|
||||||
@@ -167,7 +180,8 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
// 从数据库查询需要重试的消息
|
// 从数据库查询需要重试的消息
|
||||||
// List<MessageRetryQueueDO> retryMessages = retryQueueMapper.selectNeedRetryMessages(LocalDateTime.now(), DEFAULT_BATCH_SIZE);
|
// List<MessageRetryQueueDO> retryMessages = retryQueueMapper.selectNeedRetryMessages(LocalDateTime.now(), DEFAULT_BATCH_SIZE);
|
||||||
// 从redis中查询需要重试的消息
|
// 从redis中查询需要重试的消息
|
||||||
Set<String> needRetryMessageIds = messageRetryRedisDAO.getNeedRetryMessageIds(channel, LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), DEFAULT_BATCH_SIZE);
|
long epochMilli = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||||
|
Set<String> needRetryMessageIds = messageRetryRedisDAO.getNeedRetryMessageIds(channel, epochMilli, DEFAULT_BATCH_SIZE);
|
||||||
|
|
||||||
// 没有需要重试的消息
|
// 没有需要重试的消息
|
||||||
if (needRetryMessageIds.isEmpty()) {
|
if (needRetryMessageIds.isEmpty()) {
|
||||||
@@ -175,6 +189,7 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<MessageRecordDO> messageRecordDOList = messageRecordService.listByIds(needRetryMessageIds);
|
List<MessageRecordDO> messageRecordDOList = messageRecordService.listByIds(needRetryMessageIds);
|
||||||
|
System.out.println("messageRecordDOList.size()=:" + messageRecordDOList.size());
|
||||||
|
|
||||||
for (MessageRecordDO messageRecordDO : messageRecordDOList) {
|
for (MessageRecordDO messageRecordDO : messageRecordDOList) {
|
||||||
processSingleRetry(messageRecordDO);
|
processSingleRetry(messageRecordDO);
|
||||||
@@ -204,27 +219,21 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
public boolean updateRetryInfo(String messageId, int retryCount, LocalDateTime nextRetryTime, LocalDateTime lastRetryTime, String s) {
|
||||||
public boolean removeRetryRecord(String messageId) {
|
return this.lambdaUpdate()
|
||||||
MessageRetryQueueDO retryRecord = super.baseMapper.selectOne(
|
.set(MessageRetryQueueDO::getRetryCount, retryCount)
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageRetryQueueDO>()
|
.set(MessageRetryQueueDO::getNextRetryTime, nextRetryTime)
|
||||||
.eq(MessageRetryQueueDO::getMessageId, messageId)
|
.set(MessageRetryQueueDO::getLastRetryTime, lastRetryTime)
|
||||||
);
|
.eq(MessageRetryQueueDO::getMessageId, messageId)
|
||||||
|
.update();
|
||||||
if (retryRecord == null) {
|
}
|
||||||
log.warn("未找到重试记录: messageId={}", messageId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从数据库删除
|
|
||||||
super.baseMapper.deleteByMessageId(messageId);
|
|
||||||
|
|
||||||
// 从Redis删除
|
|
||||||
messageRetryRedisDAO.removeFromRetryQueue(retryRecord.getChannel(), messageId);
|
|
||||||
|
|
||||||
log.info("删除重试记录成功: messageId={}", messageId);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteByMessageIds(List<String> messageIds) {
|
||||||
|
return this.lambdaUpdate()
|
||||||
|
.set(MessageRetryQueueDO::getDeleted, true)
|
||||||
|
.in(CollUtil.isNotEmpty(messageIds), MessageRetryQueueDO::getMessageId, messageIds)
|
||||||
|
.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,16 +241,15 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
*/
|
*/
|
||||||
private void processSingleRetry(MessageRecordDO messageRecordDO) {
|
private void processSingleRetry(MessageRecordDO messageRecordDO) {
|
||||||
// 异步调用消息发送接口进行重试
|
// 异步调用消息发送接口进行重试
|
||||||
CompletableFuture<Boolean> sendFuture = CompletableFuture.supplyAsync(() ->
|
CompletableFuture<Boolean> sendFuture = CompletableFuture.supplyAsync(() -> messageRecordService.processSendMsg(messageRecordDO), MSG_RETRY_THREAD_POOL_EXECUTOR);
|
||||||
messageRecordService.processSendMsg(messageRecordDO), MSG_RETRY_THREAD_POOL_EXECUTOR
|
|
||||||
);
|
|
||||||
|
|
||||||
sendFuture.orTimeout(5, TimeUnit.SECONDS)
|
sendFuture.orTimeout(5, TimeUnit.SECONDS)
|
||||||
.thenAccept(sendResult -> {
|
.thenAccept(sendResult -> {
|
||||||
|
messageRecordService.updateRetryCount(messageRecordDO.getMessageId());
|
||||||
if (sendResult) {
|
if (sendResult) {
|
||||||
log.info("处理消息重试成功逻辑:messageId={}", messageRecordDO.getMessageId());
|
log.info("处理消息重试成功逻辑:messageId={}", messageRecordDO.getMessageId());
|
||||||
super.baseMapper.deleteByMessageId(messageRecordDO.getMessageId());
|
|
||||||
messageRetryRedisDAO.removeFromRetryQueue(messageRecordDO.getChannel(), messageRecordDO.getMessageId());
|
messageRetryRedisDAO.removeFromRetryQueue(messageRecordDO.getChannel(), messageRecordDO.getMessageId());
|
||||||
|
this.deleteByMessageIds(Collections.singletonList(messageRecordDO.getMessageId()));
|
||||||
} else {
|
} else {
|
||||||
// 重试失败,更新重试信息
|
// 重试失败,更新重试信息
|
||||||
log.error("处理消息重试失败逻辑:messageId={}", messageRecordDO.getMessageId());
|
log.error("处理消息重试失败逻辑:messageId={}", messageRecordDO.getMessageId());
|
||||||
@@ -249,6 +257,7 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
handleRetryFailure(messageRecordDO);
|
handleRetryFailure(messageRecordDO);
|
||||||
}
|
}
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
|
messageRecordService.updateRetryCount(messageRecordDO.getMessageId());
|
||||||
log.error("异步执行消息重试发生异常:messageId={}", messageRecordDO.getMessageId(), ex);
|
log.error("异步执行消息重试发生异常:messageId={}", messageRecordDO.getMessageId(), ex);
|
||||||
// 发生异常时也尝试处理失败逻辑,避免消息丢失
|
// 发生异常时也尝试处理失败逻辑,避免消息丢失
|
||||||
try {
|
try {
|
||||||
@@ -273,7 +282,7 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
* 处理重试失败的情况
|
* 处理重试失败的情况
|
||||||
*/
|
*/
|
||||||
private void handleRetryFailure(MessageRecordDO messageRecordDO) {
|
private void handleRetryFailure(MessageRecordDO messageRecordDO) {
|
||||||
MessageRetryQueueDO messageRetryQueueDO = this.getById(messageRecordDO.getMessageId());
|
MessageRetryQueueDO messageRetryQueueDO = this.lambdaQuery().eq(MessageRetryQueueDO::getMessageId, messageRecordDO.getMessageId()).one();
|
||||||
int newRetryCount = messageRetryQueueDO.getRetryCount() + 1;
|
int newRetryCount = messageRetryQueueDO.getRetryCount() + 1;
|
||||||
|
|
||||||
if (newRetryCount >= messageRetryQueueDO.getMaxRetry()) {
|
if (newRetryCount >= messageRetryQueueDO.getMaxRetry()) {
|
||||||
@@ -290,7 +299,7 @@ public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueM
|
|||||||
// 还可以继续重试,更新重试信息
|
// 还可以继续重试,更新重试信息
|
||||||
LocalDateTime nextRetryTime = this.calculateNextRetryTime(messageRecordDO.getChannel(), newRetryCount);
|
LocalDateTime nextRetryTime = this.calculateNextRetryTime(messageRecordDO.getChannel(), newRetryCount);
|
||||||
|
|
||||||
super.baseMapper.updateRetryInfo(
|
this.updateRetryInfo(
|
||||||
messageRecordDO.getMessageId(),
|
messageRecordDO.getMessageId(),
|
||||||
newRetryCount,
|
newRetryCount,
|
||||||
nextRetryTime,
|
nextRetryTime,
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.njcn.msgpush.module.push.sms;
|
package com.njcn.msgpush.module.push.sms;
|
||||||
|
|
||||||
|
import com.njcn.msgpush.module.push.client.factory.MessageProviderFactory;
|
||||||
|
import com.njcn.msgpush.module.push.client.sender.Sender;
|
||||||
|
import com.njcn.msgpush.module.push.client.sender.SmsSender;
|
||||||
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
|
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
|
||||||
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordReqVO;
|
import com.njcn.msgpush.module.push.controller.admin.message.vo.MessageRecordReqVO;
|
||||||
|
import com.njcn.msgpush.module.push.dal.dataobject.channel.ChannelProviderConfigDO;
|
||||||
import com.njcn.msgpush.module.push.service.message.MessageRecordService;
|
import com.njcn.msgpush.module.push.service.message.MessageRecordService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +32,13 @@ public class MsgPushClientTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MessageRecordService messageRecordService;
|
private MessageRecordService messageRecordService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("messageProviderFactoryMap")
|
||||||
|
private Map<String, MessageProviderFactory> messageProviderFactoryMap;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Sender sender;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSend() throws InterruptedException {
|
public void testSend() throws InterruptedException {
|
||||||
List<MessageRecordReqVO> messageIdList = new ArrayList<>();
|
List<MessageRecordReqVO> messageIdList = new ArrayList<>();
|
||||||
@@ -56,4 +69,17 @@ public class MsgPushClientTest {
|
|||||||
Thread.sleep(9000);
|
Thread.sleep(9000);
|
||||||
System.out.println(sendResult);
|
System.out.println(sendResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void templateSelect() {
|
||||||
|
MessageProviderFactory messageProviderFactory = messageProviderFactoryMap.get(MsgPushConstant.PROVIDER_TYPE_TELECOM);
|
||||||
|
ChannelProviderConfigDO config = new ChannelProviderConfigDO();
|
||||||
|
config.setApiUrl("https://sms.ymeeting.cn/smsv2");
|
||||||
|
config.setAppKey("925631");
|
||||||
|
config.setAppSecret("AMW2pOVrdky");
|
||||||
|
config.setExtno("106905631");
|
||||||
|
SmsSender smsSender = messageProviderFactory.createSmsSender(config, sender);
|
||||||
|
String templateIdentifier = "SMS_481710295";
|
||||||
|
smsSender.queryTemplate(templateIdentifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user