郑重声明:
本汪作为一名资深的哈士奇,
每天除了闲逛,拆家,就是啃博客了
作为不是在戏精,就是在戏精的路上的二哈
今天就来啃啃RabbitMQ邮件100%发送成功这块小骨头吧
开篇有益:
为什么要用:
压测,20万条数据,每秒2000条邮件发送,连发100秒,不使用RabbitMQ,全走数据库,你懂的
使用RabbitMQ
spring.rabbitmq.listener.simple.concurrency=10
spring.rabbitmq.listener.simple.max-concurrency=20
20个channel,大家均担,每秒2000/20 = 100条,连发200秒
或者40个channel,大家均担,每秒2000/40=50条,连发200秒
DB压力骤减,有木有
本汪带大家先看看什么是RabbitMQ:
消息队列,他官网是这样吹的:
1.异步消息,支持多种消息传递协议,消息排队,传递确认,到队列的灵活路由,多种交换类型。
2.使用BOSH,Chef,Docker和Puppet进行部署。使用喜欢的编程语言来开发跨语言消息传递,例如Java,.NET,PHP,Python,JavaScript,Ruby,Go 以及许多其他语言。
3.分布式部署,部署为集群以实现高可用性和吞吐量;跨多个可用区域和区域联合。
本汪总结下:轻巧,多协议支持,高可用,总之就是很牛了,号称最受欢迎的开源消息代理之一
依旧老样子,先附上思维导图和源码链接,然后我们再来谈:
https://github.com/HuskyCorps/distributeMiddleware
本汪建议,真想学的话,源码一定要荡下来去看
思维导图:
进入实例讲解:
本汪带大家一起去看看,实际操作起来,他是什么样子的
(一)本汪先给大家讲一下RabbitMQTemplate的配置情况
看起来很多,但其实没有什么,就四点
1.自定义RabbitMQ模板
2.配置单一实例消费者(Bean注入)
3.配置多实例消费者(Bean注入)
4.配置邮件发送的交换机、路由及其绑定
本汪尽量将注释标注在代码处,方便大家查看学习
,
对于大块的注释我也只能另提出去,放在代码最后了,不然实在影响理解逻辑
package com.tencent.bigdata.convenience.server.config;
import com.tencent.bigdata.convenience.server.enums.Constant;
import com.tencent.bigdata.convenience.server.service.log.MsgLogService;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* rabbitmq模板操作bean组件示例的自定义注入配置
*
* @author Yuezejian
* @date 2020年 08月23日 10:11:17
*/
//在消息发送、接受时记录DB日志,定时轮训DB日志,
// 查明哪些发送消息没有成功消费,启动重新发送消息机制,进行消息补偿
@Configuration
public class RabbitmqTemplate {
private static final Logger log = LoggerFactory.getLogger(RabbitTemplate.class);
@Autowired
private MsgLogService msgLogService;
@Autowired
private Environment env;
@Autowired
private CachingConnectionFactory connectionFactory;
//默认配置
@Autowired
private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;
//rabbitmq自定义注入模板操作组件
@Bean
public RabbitTemplate rabbitTemplate() {
connectionFactory.setPublisherConfirms(env.getProperty("spring.rabbitmq.publisher-confirms",boolean.class));//发送确认
connectionFactory.setPublisherReturns(env.getProperty("spring.rabbitmq.publisher-returns",boolean.class));//如果消息丢失,需要有所返回
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 触发setReturnCallback回调必须设置mandatory=true,
// 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
rabbitTemplate.setMandatory(true);
//确认消息发送成功,还是发送丢失
//ConfirmCallback 只确认消息是否正确到达 Exchange 中
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* @param correlationData 消息唯一标识
* @param ack 确认结果
* @param cause 失败原因
*/
@SneakyThrows
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("消息发送到Exchange————情况反馈:唯一标识:correlationData={},消息确认:ask={},失败原因:cause={}",correlationData,true,cause);
if (correlationData != null && StringUtils.isNotBlank(correlationData.getId())) {
if (ack) {
//TODO:成功发送到Exchange中后,更新消息发送的状态为投递————成功状态
msgLogService.updateStatus(correlationData.getId(), Constant.DELIVER_SUCCESS);
} else {
//TODO:发送到Exchange中失败后,更新消息发送的状态为投递————失败状态
msgLogService.updateStatus(correlationData.getId(), Constant.DELIVER_FALSE);
log.info("消息发送到Exchange--失败:correlationData={},ask={},cause={}", correlationData, true, cause);
}
}
}
});
//ReturnCallback 消息是否从Exchange路由到Queue, 注意: 这是一个失败回调,
// 只有消息从Exchange路由到Queue失败才会回调这个方法
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 标记消息
* @param replyCode 响应码
* @param replyText 响应内容
* @param exchange 交换机
* @param rountingKey 路由
*/
@Override
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange, String rountingKey) {
log.warn("消息由Exchange发送到Queue时失败:message={},replyCode={},replyText={},exchange={},rountingKey={}",
message,replyCode,replyText,exchange,rountingKey);
}
});
return rabbitTemplate;
}
/**
* 单一实例消费者(一条队列只会有一个消费者,一条消费者线程监听)
* 当前仅日志记录用了此例,AcknowledgeMode.NONE
* @return
*/
@Bean(name = "singleListenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
//设置缓存连接,AbstractRabbitListenerContainerFactory
factory.setConnectionFactory(connectionFactory);
//生产者服务将JSON类型的数据传递到对应的队列, 而消费端处理器中接收到的数据类型也是JSON类型
//消息转换器使用了RabbitMQ自带的Jackson2JsonMessageConverter转换器
factory.setMessageConverter(new Jackson2JsonMessageConverter());
//确认消费模式-NONE自动确认 -AUTO根据情况确认 -MANUAL手动确认
//默认情况下消息消费者是自动 ack (确认)消息的,需要设置为手动确认
// 原因是:自动确认会在消息发送给消费者后立即确认,这样存在丢失消息的可能。
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
//设置并发
factory.setConcurrentConsumers(1);
//最大并发
factory.setMaxConcurrentConsumers(1);
factory.setPrefetchCount(1);
factory.setTxSize(1);
return factory;
}
/**
* 多实例消费者(一条队列有多个消费者实例,多个消费者线程进行监听和出路)
* 当前仅邮件发送用来此例,AcknowledgeMode.MANUAL
* @return
*/
@Bean(name = "multiListenerContainer")
public SimpleRabbitListenerContainerFactory multiListenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factoryConfigurer.configure(factory,connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
//确认消费模式-NONE自动确认 -AUTO根据情况确认 -MANUAL手动确认
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.concurrency",int.class));
factory.setMaxConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.max-concurrency",int.class));
factory.setPrefetchCount(env.getProperty("spring.rabbitmq.listener.simple.prefetch",int.class));
return factory;
}
/**
* 邮件发送————预先创建交换机、路由及其绑定
* @return
*/
@Bean
public DirectExchange mailExchange() {
return new DirectExchange( env.getProperty("mq.email.exchange"),true,false);
}
@Bean
public Queue mailQueue() {
return new Queue(env.getProperty("mq.email.queue"),true);
}
@Bean
public Binding mailBinding() {
return BindingBuilder.bind(mailQueue()).to(mailExchange()).with(env.getProperty("mq.email.routing.key"));
}
}
这儿本汪稍微给小白说下,知道的自行跳过: Environment 接口,位于package org.springframework.core.env;包下,继承自PropertyResolver,有获取可利用资源,获取默认资源,接受传入资源三个方法,此处使用到的是其父类PropertyResolver的getProperty()方法底层如下:
public <T> T getProperty(String key, Class<T> targetType) {
return this.propertyResolver.getProperty(key, targetType);
}
此处是以key,和key的类型来获取 application.properties 文件中对rabbitMQ的配置,这边本汪是都提了出去,方便后期的修改
application.properties里配置如下:
#消息发送确认
spring.rabbitmq.publisher-confirms=true
#消息发送队列回调
spring.rabbitmq.publisher-returns=true
mq.email.queue=${
mq.env}.email.queue
mq.email.exchange=${
mq.env}.email.exchange
mq.email.routing.key=${
mq.env}.email.routing.key
到这儿,这块应该就差不多都看懂了
那么本汪继续往下,我们接着给RabbitMQ拆家,看我拆家小能手咖啡汪的厉害
(二)本汪先给大家看一下Controller里都干了什么
controller里,就是对刚发的邮件进行日志记录,然后发他发送到RabbitMQ的队列中,等着被监听消费(把货物摆到货架上,等需要的人来往走拿)
其实依旧很简单,就两点
1.对即将发出的消息做记录,此时邮件状态为发送中
2.把邮件当消息塞入MQ里,把他扔到对应的队列Queue里,等着被消费者拉出去一顿操作,对,就是你想的这样
package com.tencent.bigdata.convenience.server.controller;
import cn.hutool.core.lang.Snowflake;
import com.google.gson.Gson;
import com.tencent.bigdata.convenience.api.response.BaseResponse;
import com.tencent.bigdata.convenience.api.response.StatusCode;
import com.tencent.bigdata.convenience.model.entity.MsgLog;
import com.tencent.bigdata.convenience.server.enums.Constant;
import com.tencent.bigdata.convenience.server.request.MailRequest;
import com.tencent.bigdata.convenience.server.service.log.LogAopAnnotation;
import com.tencent.bigdata.convenience.server.service.log.MsgLogService;
import com.tencent.bigdata.convenience.server.service.mail.ResendMsg;
import com.tencent.bigdata.convenience.server.utils.ValidatorUtil;
import jodd.util.StringUtil;
import org.joda.time.DateTime;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* 邮件发送
*
* @author Yuezejian
* @date 2020年 08月24日 19:36:55
*/
@RestController
@RequestMapping("mail")
public class MailController extends AbstractController {
//引入RabbitTemplate默认模板,
//她位于package org.springframework.amqp.rabbit.core;下,
//提供了一些默认的配置,DEFAULT_REPLY_TIMEOUT = 5000L;
//String DEFAULT_ENCODING = "UTF-8";等等
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private Environment env;
@Autowired
private MsgLogService msgLogService;
public static final Snowflake snowflake = new Snowflake(3,2);
@RequestMapping("send/mq")
@LogAopAnnotation("发送邮件")
public BaseResponse stringData(@RequestBody @Validated MailRequest request, BindingResult result) {
String checkRes = ValidatorUtil.checkResult(result);
if (StringUtil.isNotBlank(checkRes)) {
return new BaseResponse(StatusCode.InvalidParams.getCode(),checkRes);
}
BaseResponse response = new BaseResponse(StatusCode.Success);
try {
//雪花算法,生成全局唯一主键
//41bit时间戳 + 10bit工作机器id + 12bit序列号
String msgId = snowflake.nextIdStr();
request.setCorrelationData(msgId);
final String msg = new Gson().toJson(request);
//先置消息发送状态为:Constant.DELIVER_LOADING,即消息发送中
//设置重试次数为0L
MsgLog entity = new MsgLog(msgId,msg,env.getProperty("mq.email.exchange"),
env.getProperty("mq.email.routing.key"), Constant.DELIVER_LOADING, DateTime.now().toDate(), DateTime.now().toDate());
entity.setTryCount(0L);
//TODO:消息发送前,先将记录存入数据库
msgLogService.recordLog(entity);
//TODO:将类的对象塞入MQ,设置消息头,监听时用类的方法去接收
ResendMsg.send(entity, request, rabbitTemplate);
} catch (Exception e) {
log.error("异常信息",e);
response = new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
}
(三)本汪给大家看一下监听-消费者是怎么配置的
package com.tencent.bigdata.convenience.server.service.mail;
import com.rabbitmq.client.Channel;
import com.tencent.bigdata.convenience.model.entity.MsgLog;
import com.tencent.bigdata.convenience.model.mapper.MsgLogMapper;
import com.tencent.bigdata.convenience.server.enums.Constant;
import com.tencent.bigdata.convenience.server.request.MailRequest;
import com.tencent.bigdata.convenience.server.service.log.MsgLogService;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 邮件监听器-消费者
*
* @author Yuezejian
* @date 2020年 08月24日 20:23:50
*/
@Component
public class MailMqListener {
public static final Logger log = LoggerFactory.getLogger(MailMqListener.class);
@Autowired
private MailService mailService;
@Autowired
private MsgLogService msgLogService;
@Autowired
private MsgLogMapper msgLogMapper;
//监听邮件发送的消息
@RabbitListener(queues = {
"${mq.email.queue}"},containerFactory = "multiListenerContainer")
public void consume(Message message, MailRequest entity, Channel channel) throws IOException, InterruptedException {
MessageProperties properties = message.getMessageProperties();
Long tag = properties.getDeliveryTag();
Long tryCount=0L;
String msgId = "";
// MailRequest entity = new Gson().fromJson(message.getBody().toString(),MailRequest.class);
msgId = entity.getCorrelationData();
log.info("————————————————————监听到消息,消息内容: message={}",entity);
MsgLog msgLog = msgLogMapper.selectByPrimaryKey(msgId);
//消费幂等性
//这儿本汪说一下,
//什么是需要用定时器重新投递的?1.没有成功发入队列的 2.成功进入队列(但数据丢失等原因,无法被正常监听消费的) -> 才需要重新投递
//什么是需要进行发送重试的?已经成功发入了队列,并被消费,但是邮件发送出了异常,没有成功收到的 ->才需要重试发送(每10秒重试一次)
//状态为投递成功的,我们进行邮件发送
//监听到的消息,肯定都是投递成功的
try {
if (msgLog != null && !Constant.CONSUME_SUCCESS.equals(msgLog.getStatus()) && msgLog.getTryCount() < Constant.MAIL_RESEND_MAX_TIME) {
tryCount = msgLog.getTryCount();
//核心业务逻辑【消息处理~发送邮件(同步;异步)】
boolean res = mailService.sendSimpleEmail(msgId, entity.getSubject(), entity.getContent(),
StringUtils.split(entity.getUserMails(), ","));//用逗号隔开实现邮件的群发
if (res) {
//确认消费
//channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false);
//deliveryTag:该消息的index
//multiple:是否批量 true:将一次性ack所有小于deliveryTag的消息。
channel.basicAck(tag, false);// 消费确认,只确认当前一条
return;
}
}
} catch (Exception e) {
e.printStackTrace();
//出现异常时可以走重试机制 ~ 重试次数为3次,每次间隔10s
// (考虑到RabbitMQ消息延时会有3~4second,可以将休眠时间设为6~7second,确保每10second左右发送一次)
if (tryCount < Constant.MAIL_RESEND_MAX_TIME) {
Thread.sleep(Constant.NEXT_TRY_TIME_AFTER);
//channel.basicNack(tag, false, true)
//deliveryTag:该消息的index
//multiple:是否批量 true:将一次性拒绝所有小于deliveryTag的消息。
//requeue:被拒绝的是否重新入队列
//重入队列的消息,会被再次重试发送,每重试一次,重试次数加“1”
channel.basicNack(tag,false,true);
tryCount +=1L;
msgLogMapper.updateTryCount(msgId,tryCount, DateTime.now().toDate());
return;
}
}
//当走到这一步时,代表消息已被消费(status=3)或者 重试次数达到上限 等情况-但不管是哪种情况,
// 都需要将消息从队列中移除,防止下次项目重启时重新监听消费
channel.basicAck(tag,false);//消费确认,只确认当前一条
}
}
service
(四) 邮件发送service
JavaMailSender 位于package org.springframework.mail.javamail;包下,需要依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>1.5.7.RELEASE</version>
</dependency>
Mailservice:
package com.tencent.bigdata.convenience.server.service.mail;
import com.google.gson.Gson;
import com.tencent.bigdata.convenience.model.mapper.MsgLogMapper;
import com.tencent.bigdata.convenience.server.controller.AbstractController;
import com.tencent.bigdata.convenience.server.enums.Constant;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
/**
* 邮件service
*
* @author Yuezejian
* @date 2020年 08月24日 20:23:04
*/
@Service
public class MailService extends AbstractController {
@Autowired
private Environment env;
@Autowired
private JavaMailSender mailSender;
@Autowired
private MsgLogMapper msgLogMapper;
//TODO:发送简单的邮件消息
//@Async("threadPoolTaskExecutor")此处切记不可使用AOP注解,方法进入切面后,由于@Around,返回类型boolean会被设为void,会造成异常
//Caused by: org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type
//仍想使用异步线程处理,可以修改ACK实现方法,将返回类型设为void即可
//或对mailSender.send做包装
public Boolean sendSimpleEmail(final String msgId,final String subject,final String content,final String ... tos) throws Exception {
Boolean res = true;
SimpleMailMessage message=new SimpleMailMessage();
message.setSubject(subject);
message.setText(content);
message.setTo(tos);
message.setFrom(env.getProperty("mail.send.from"));
try {
mailSender.send(message);
} catch (MailException e) {
log.error("邮件发送失败, to={}, title={}, e={}", new Gson().toJson(tos), subject, e);
res = false;
throw e;
} finally {
this.updateMsgSendStatus(msgId,res);
}
return res;
}
//TODO:更新消息处理的结果
private void updateMsgSendStatus(final String msgId,Boolean res){
if (StringUtils.isNotBlank(msgId)){
if (res){
msgLogMapper.updateStatus(msgId, Constant.CONSUME_SUCCESS);
}else{
msgLogMapper.updateStatus(msgId, Constant.CONSUME_FALSE);
}
}
}
}
(五)使用定时器,对发送失败邮件进行重新发送
package com.tencent.bigdata.convenience.server.service.mail;
import com.google.gson.Gson;
import com.tencent.bigdata.convenience.model.entity.MsgLog;
import com.tencent.bigdata.convenience.server.enums.Constant;
import com.tencent.bigdata.convenience.server.request.MailRequest;
import com.tencent.bigdata.convenience.server.service.log.MsgLogService;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* RabbitMQ邮件定时重发
*
* @author Yuezejian
* @date 2020年 08月29日 22:03:11
*/
@Component
public class ResendMsg {
public static final Logger log = LoggerFactory.getLogger(ResendMsg.class);
@Autowired
private MsgLogService msgLogService;
@Autowired
private RabbitTemplate rabbitTemplate;
@SneakyThrows
@Scheduled(cron = "0 0/2 * * * ?")
// @Scheduled(cron = "0 0 0/1 * * ?")
// @Scheduled(cron = "0 0/30 * * * ?")
public void resend() {
try {
log.info("开始执行定时任务(重新投递邮件)");
List<MsgLog> msgLogs = null;
//获取需要重新投递的(状态为投递中/投递失败,或为投递成功,但限定时间内仍未被正常消费的)
String status = Constant.DELIVER_LOADING + "," + Constant.DELIVER_FALSE + "," + Constant.DELIVER_SUCCESS;
msgLogs = msgLogService.selectTimeoutMsg(status);
if (msgLogs != null && !msgLogs.isEmpty()) {
msgLogs.forEach(msgLog -> {
if (StringUtils.isNotBlank(msgLog.getMsg()) &&
StringUtils.isNotBlank(msgLog.getExchange()) &&
StringUtils.isNotBlank(msgLog.getRoutingKey())) {
//反序列化,日志记录中的msg是进过序列化的MailRequest对象
MailRequest request = new Gson().fromJson(msgLog.getMsg(),MailRequest.class);
//重新投递
send(msgLog, request, rabbitTemplate);
log.info("重新投递--成功--消息id={}", msgLog.getMsgId());
}
});
}
} catch (Exception e) {
log.error("定时任务重新拉取投递失败的消息~重新进行投递~发生异常:",e.fillInStackTrace());
}
}
public static void send(MsgLog msgLog, MailRequest request, RabbitTemplate rabbitTemplate) {
rabbitTemplate.setExchange(msgLog.getExchange());
rabbitTemplate.setRoutingKey(msgLog.getRoutingKey());
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.convertAndSend(request, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置消息持久化和消息头的类型
MessageProperties properties = message.getMessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);//设置持久化
properties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, MailRequest.class);
return message;
}
});
}
}
(六)MsgLogService,如果真的什么都懒的做的话,Ctrl+C,你们心中想的,本汪都懂
package com.tencent.bigdata.convenience.server.service.log;
import com.google.gson.Gson;
import com.tencent.bigdata.convenience.model.entity.MsgLog;
import com.tencent.bigdata.convenience.model.mapper.MsgLogMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
* rabbitMq消息发送情况日志记录
*
* @author Yuezejian
* @date 2020年 08月27日 14:06:17
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class MsgLogService {
private static final Logger logger = LoggerFactory.getLogger(MsgLogService.class);
@Autowired
private MsgLogMapper logMapper;
//记录日志
// @Async("threadPoolTaskExecutor")
public void recordLog(MsgLog log) throws Exception {
logger.info("正在-新增-消息发送日志 MsgLog={}",new Gson().toJson(log));
logMapper.insertSelective(log);
logger.info("新增-消息发送日志----成功 ");
}
//更新修改日志
public int updateLog(MsgLog log){
return logMapper.updateByPrimaryKeySelective(log);
}
/**
* 更据邮件发送记录中,邮件的状态和重发的次数,筛选需要重发的邮件
* (状态不为“3L”即发送不成功,重试次数小于3)
* @param status 邮件发送状态——邮件投递中+邮件投递失败
* @return
* @throws Exception
*/
public List<MsgLog> selectTimeoutMsg(String status) throws Exception {
logger.info("查询超时消息列表");
String arr[] = StringUtils.split(status,",");
Long deliverLoading = Long.valueOf(arr[0]);//发送状态为投递中
Long deliverFalse = Long.valueOf(arr[1]);//发送状态为投递失败
Long deliverSuccess = Long.valueOf(arr[2]);//发送状态为投递成功,
// 但数据以及损坏或丢失,而无法被正常消费,比如RabbitMQ崩溃,本汪怎么说,应该懂得啊,不可预料因素
return logMapper.selectTimeoutMsg(deliverLoading, deliverFalse,deliverSuccess);
}
/**
*
* @param msgId
* @param flag
* @throws Exception
*/
public void updateStatus(String msgId, long flag) throws Exception {
logMapper.updateStatus(msgId,flag);
}
public void updateTryCount(String msgId,Long tryCount, Date nextTryTime) throws Exception {
logMapper.updateTryCount(msgId,tryCount,nextTryTime);
}
}
(七)BUG解决,不自己加最后一个构造函数,会有BUG
JSON parse error: Cannot construct instance of XXXXXX (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ,
rabbitMQ会有消息无法消费,一直积压
package com.tencent.bigdata.convenience.server.request;
import com.google.gson.Gson;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import java.io.IOException;
import java.io.Serializable;
/**
* 邮件
*
* @author Yuezejian
* @date 2020年 08月24日 19:39:42
*/
@Data
public class MailRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String correlationData;//RabbitMQ queue唯一标识符
@NotBlank(message = "用户邮箱不能为空!")
private String userMails;
@NotBlank(message = "邮箱主题不能为空!")
private String subject;
@NotBlank(message = "邮箱内容不能为空!")
private String content;
public MailRequest(String correlationData, String userMails, String subject, String content) {
this.correlationData = correlationData;
this.userMails = userMails;
this.subject = subject;
this.content = content;
}
/**
* 为了解决一下问题
* Warning:no String-argument constructor/factory
* method to deserialize from String value
* @param json
* @throws IOException
*/
public MailRequest(String json) throws IOException {
MailRequest param = new Gson().fromJson(json, MailRequest.class);
this.correlationData = param.getCorrelationData();
this.userMails = param.getUserMails();
this.subject = param.getSubject();
this.content = param.getContent();
}
}
(八)RabbitMQ邮件发送知识拓展
1.550,503异常
2.怎么申请邮件
3.163邮箱对邮件标题含“test”敏感词汇自动拦截
4.阿里云服务器,62端口占用解决办法
最后,代码中涉及到的BaseResponse,统一响应模型,以及RabbimMQ,AOP日志记录等代码,想了解的可以自行查看本汪相关博客
AOP日志记录:https://blog.csdn.net/weixin_42994251/article/details/108302819
统一响应模型:https://blog.csdn.net/weixin_42994251/article/details/108302377