All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lx.boot.mq.RabbitUtil Maven / Gradle / Ivy

package com.lx.boot.mq;

import com.lx.boot.OS;
import com.lx.boot.email.EmailUtil;
import com.lx.constant.DefaultBaseConstant;
import com.lx.util.LX;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import static com.lx.constant.DefaultBaseConstant.FIRM_ID;


/**
 * 默认开启mq mq.enable
 * 关闭可以使用
 * mq.enable=false
 *
 **/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "mq.enable", havingValue = "true", matchIfMissing = false)
@ConditionalOnClass(RabbitTemplate.class)
public class RabbitUtil {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /** 说明:发送延迟消息
     * @author ylx 2023/6/19 16:22 */
    public void sendDelayMessage(String className, String methodName, Object msg) {
        MqInfo mq = new MqInfo("任务" + LX.md5(className + methodName).substring(0, 10), 0, null, OS.getApplicationName(), className, methodName);
        mq.setMsg(msg);
        sendDelayMessage(mq);
    }
    /** 说明:发送延迟消息
     * @author ylx 2023/6/19 16:22 */
    public void sendDelayMessage(String className, String methodName, Object msg,long delay,long [] retryList) {
        MqInfo mq = new MqInfo("任务" + LX.md5(className + methodName).substring(0, 10), delay, retryList, OS.getApplicationName(), className, methodName);
        mq.setMsg(msg);
        sendDelayMessage(mq);
    }
    //说明: 发送延迟消息
    /**{ ylx } 2021/6/2 15:33 */
    public void sendDelayMessage(MqInfo mq) {
        log.info("消息:"+mq);
        if (mq.getDelay()>0){
            this.rabbitTemplate.convertAndSend("delay_exchange", mq.getServiceName()+"_delay_key", mq,
                    message -> {
                        //注意这里时间可以使long,而且是设置header
                        message.getMessageProperties().setHeader("x-delay", mq.getActualDelay() * 1000L);
                        return message;
                    });
        }else{
            this.rabbitTemplate.convertAndSend("delay_exchange", mq.getServiceName()+"_delay_key", mq);
        }

    }
    /**
     * 默认情况下,如果没有配置手动ACK, 那么Spring Data AMQP 会在消息消费完毕后自动帮我们去ACK
     * 存在问题:如果报错了,消息不会丢失,但是会无限循环消费,一直报错,如果开启了错误日志很容易就吧磁盘空间耗完
     * 解决方案:手动ACK,或者try-catch 然后在 catch 里面将错误的消息转移到其它的系列中去
     * spring.rabbitmq.listener.simple.acknowledge-mode = manual
     * @param list 监听的内容
     */
    @RabbitListener(queues = "${spring.application.name}_delay_queue")
    public void cfgUserReceiveDealy(MqInfo mqInfo, Message message, Channel channel) throws IOException {
        OS.setLogTraceId(mqInfo.getRequestId());
        log.info("===============接收队列接收消息====================");
        log.info("接收时间:{},接受内容:{}", LocalDateTime.now(), mqInfo.toString());
        try {
            OS.put(FIRM_ID,mqInfo.getFirmId());
            boolean success = true;
            Object bean = OS.getBean(mqInfo.getClassName());
            LX.exObj(bean, "调用对象不存在!" + mqInfo.getClassName());
            Method method;
            if (mqInfo.getMsg() == null) {
                method = bean.getClass().getDeclaredMethod(mqInfo.getMethodName());
            } else {
                method = bean.getClass().getDeclaredMethod(mqInfo.getMethodName(), mqInfo.getMsg().getClass());
            }
            LX.exObj(method, "调用对象方法不存在!" + mqInfo.getClassName() + "." + mqInfo.getMethodName());
            // 调用方法 如果有问题报错即可 会进行重新尝试
            method.invoke(bean, mqInfo.getMsg());
            //通知 MQ 消息已被接收,可以ACK(从队列中删除)了
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("============消费失败,尝试消息补发再次消费!==============",e);
            retry(mqInfo, message, channel);
        }
    }
    //说明:消费失败
    /**{ ylx } 2022/3/8 18:02 */
    private void retry(MqInfo mqInfo, Message message, Channel channel){
        log.error("============消费失败,尝试消息补发再次消费!==============");
        try {
            if (mqInfo.addCount()){
                sendDelayMessage(mqInfo);
            }else{
                EmailUtil.sendEmail(LX.LOCALHOST_IP+" MQ消息推送失败!"+mqInfo.getNameOfFunction() ,OS.getProperty(DefaultBaseConstant.ES_HOSTS)+" : " +OS.getLogTraceId(), OS.getProperty(DefaultBaseConstant.MQ_EMAIL_USERS_LIST,"").split(","));
                this.rabbitTemplate.convertAndSend("delay_exchange", "deadletterQueue", mqInfo);
                log.error("重试多次失败!"+mqInfo);
            }
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }catch (Exception e1){
            log.error("重试发生失败!"+mqInfo);
        }
    }

    /**
     * 延时队列交换机
     * 注意这里的交换机类型:CustomExchange
     * @return
     */
    @Bean
    public CustomExchange delayExchange(){
        Map args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange("delay_exchange","x-delayed-message",true, false,args);
    }

    /**
     * 延时队列
     * @return
     */
    @Bean
    public Queue delayQueue(){
        return new Queue(OS.getApplicationName()+"_delay_queue",true);
    }

    /**
     * 给延时队列绑定交换机
     * @return
     */
    @Bean
    public Binding cfgDelayBinding(Queue delayQueue, CustomExchange delayExchange){
        return BindingBuilder.bind(delayQueue).to(delayExchange).with(OS.getApplicationName()+"_delay_key").noargs();
    }

    /**  死信队列*/
    @Bean
    public Queue deadletterQueue(){
        return new Queue("deadletterQueue",true);
    }

    /**
     * 给死信队列绑定交换机
     * @return
     */
    @Bean
    public Binding deadletterQueueBinding(Queue deadletterQueue, CustomExchange delayExchange){
        return BindingBuilder.bind(deadletterQueue).to(delayExchange).with("deadletterQueue").noargs();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy